From 0b3a0e600a6ae8bf481b3fa0874ab3d70c942f86 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sat, 27 Jan 2024 22:29:37 +0100 Subject: [PATCH 001/112] path: Initial cost field implementation. --- libopenage/pathfinding/CMakeLists.txt | 2 + libopenage/pathfinding/cost_field.cpp | 36 ++++++++++++++ libopenage/pathfinding/cost_field.h | 69 +++++++++++++++++++++++++++ libopenage/pathfinding/types.cpp | 10 ++++ libopenage/pathfinding/types.h | 29 +++++++++++ 5 files changed, 146 insertions(+) create mode 100644 libopenage/pathfinding/cost_field.cpp create mode 100644 libopenage/pathfinding/cost_field.h create mode 100644 libopenage/pathfinding/types.cpp create mode 100644 libopenage/pathfinding/types.h diff --git a/libopenage/pathfinding/CMakeLists.txt b/libopenage/pathfinding/CMakeLists.txt index ad828995c9..77ff7ce051 100644 --- a/libopenage/pathfinding/CMakeLists.txt +++ b/libopenage/pathfinding/CMakeLists.txt @@ -1,6 +1,8 @@ add_sources(libopenage a_star.cpp + cost_field.cpp heuristics.cpp path.cpp tests.cpp + types.cpp ) diff --git a/libopenage/pathfinding/cost_field.cpp b/libopenage/pathfinding/cost_field.cpp new file mode 100644 index 0000000000..976c55b3c1 --- /dev/null +++ b/libopenage/pathfinding/cost_field.cpp @@ -0,0 +1,36 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#include "cost_field.h" + +#include "error/error.h" + + +namespace openage::path { + +CostField::CostField(size_t size) : + size{size}, + cells(this->size * this->size, 1) { +} + +cost_t CostField::get_cost(size_t x, size_t y) const { + return this->cells[x + y * this->size]; +} + +void CostField::set_cost(size_t x, size_t y, cost_t cost) { + this->cells[x + y * this->size] = cost; +} + +const std::vector &CostField::get_costs() const { + return this->cells; +} + +void CostField::set_costs(std::vector &&cells) { + ENSURE(cells.size() == this->cells.size(), + "cells vector has wrong size: " << cells.size() + << "; expected: " + << this->cells.size()); + + this->cells = std::move(cells); +} + +} // namespace openage::path diff --git a/libopenage/pathfinding/cost_field.h b/libopenage/pathfinding/cost_field.h new file mode 100644 index 0000000000..1231517bdd --- /dev/null +++ b/libopenage/pathfinding/cost_field.h @@ -0,0 +1,69 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#pragma once + +#include +#include + +#include "pathfinding/types.h" + + +namespace openage::path { + +/** + * Cost field in the flow-field pathfinding algorithm. + */ +class CostField { +public: + /** + * Create a square cost field with a specified size. + * + * @param size Length of one side of the square field. + */ + CostField(size_t size); + + /** + * Get the cost at a specified position. + * + * @param x X coordinate. + * @param y Y coordinate. + * @return Cost at the specified position. + */ + cost_t get_cost(size_t x, size_t y) const; + + /** + * Set the cost at a specified position. + * + * @param x X coordinate. + * @param y Y coordinate. + * @param cost Cost to set. + */ + void set_cost(size_t x, size_t y, cost_t cost); + + /** + * Get the cost field values. + * + * @return Cost field values. + */ + const std::vector &get_costs() const; + + /** + * Set the cost field values. + * + * @param cells Cost field values. + */ + void set_costs(std::vector &&cells); + +private: + /** + * Length of one side of the square cost field. + */ + size_t size; + + /** + * Cost field values. + */ + std::vector cells; +}; + +} // namespace openage::path diff --git a/libopenage/pathfinding/types.cpp b/libopenage/pathfinding/types.cpp new file mode 100644 index 0000000000..6428dd7ded --- /dev/null +++ b/libopenage/pathfinding/types.cpp @@ -0,0 +1,10 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#include "types.h" + + +namespace openage::path { + +// this file is intentionally empty + +} // namespace openage::path diff --git a/libopenage/pathfinding/types.h b/libopenage/pathfinding/types.h new file mode 100644 index 0000000000..cfc23941ef --- /dev/null +++ b/libopenage/pathfinding/types.h @@ -0,0 +1,29 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#pragma once + +#include + + +namespace openage::path { + +/** + * Movement cost in the cost field. + * + * 0: uninitialized + * 1-254: normal cost + * 255: impassable + */ +using cost_t = uint8_t; + +/** + * Total integrated cost in the integration field. + */ +using integrate_t = uint16_t; + +/** + * Flow field direction value. + */ +using flow_dir_t = uint8_t; + +} // namespace openage::path From c4b1094d8958ce4bd3c5026b3064b04012e6c02d Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 4 Feb 2024 18:11:15 +0100 Subject: [PATCH 002/112] path: Define common cost values. --- libopenage/pathfinding/CMakeLists.txt | 1 + libopenage/pathfinding/definitions.cpp | 10 +++++++ libopenage/pathfinding/definitions.h | 40 ++++++++++++++++++++++++++ 3 files changed, 51 insertions(+) create mode 100644 libopenage/pathfinding/definitions.cpp create mode 100644 libopenage/pathfinding/definitions.h diff --git a/libopenage/pathfinding/CMakeLists.txt b/libopenage/pathfinding/CMakeLists.txt index 77ff7ce051..e8337b7e59 100644 --- a/libopenage/pathfinding/CMakeLists.txt +++ b/libopenage/pathfinding/CMakeLists.txt @@ -1,6 +1,7 @@ add_sources(libopenage a_star.cpp cost_field.cpp + definitions.cpp heuristics.cpp path.cpp tests.cpp diff --git a/libopenage/pathfinding/definitions.cpp b/libopenage/pathfinding/definitions.cpp new file mode 100644 index 0000000000..6428dd7ded --- /dev/null +++ b/libopenage/pathfinding/definitions.cpp @@ -0,0 +1,10 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#include "types.h" + + +namespace openage::path { + +// this file is intentionally empty + +} // namespace openage::path diff --git a/libopenage/pathfinding/definitions.h b/libopenage/pathfinding/definitions.h new file mode 100644 index 0000000000..47660a15bb --- /dev/null +++ b/libopenage/pathfinding/definitions.h @@ -0,0 +1,40 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#pragma once + +#include + +#include "pathfinding/types.h" + + +namespace openage::path { + +/** + * Init value for a cells in the cost grid. + * + * Should not be used for actual costs. + */ +constexpr cost_t COST_INIT = 0; + +/** + * Minimum possible cost for a passable cell in the cost grid. + */ +constexpr cost_t COST_MIN = 1; + +/** + * Maximum possible cost for a passable cell in the cost grid. + */ +constexpr cost_t COST_MAX = 254; + +/** + * Cost value for an impassable cell in the cost grid. + */ +constexpr cost_t COST_IMPASSABLE = 255; + + +/** + * Unreachable value for a cells in the integration grid. + */ +constexpr integrate_t INTEGRATE_UNREACHABLE = std::numeric_limits::max(); + +} // namespace openage::path From dea91a752423dfaf2a0fda25f0bcac4b16bba26a Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 4 Feb 2024 19:14:37 +0100 Subject: [PATCH 003/112] path: Initial integration field implementation. --- libopenage/pathfinding/CMakeLists.txt | 1 + libopenage/pathfinding/cost_field.cpp | 12 +- libopenage/pathfinding/cost_field.h | 15 ++ libopenage/pathfinding/definitions.h | 5 + libopenage/pathfinding/integration_field.cpp | 146 +++++++++++++++++++ libopenage/pathfinding/integration_field.h | 75 ++++++++++ 6 files changed, 253 insertions(+), 1 deletion(-) create mode 100644 libopenage/pathfinding/integration_field.cpp create mode 100644 libopenage/pathfinding/integration_field.h diff --git a/libopenage/pathfinding/CMakeLists.txt b/libopenage/pathfinding/CMakeLists.txt index e8337b7e59..b40027bdaa 100644 --- a/libopenage/pathfinding/CMakeLists.txt +++ b/libopenage/pathfinding/CMakeLists.txt @@ -3,6 +3,7 @@ add_sources(libopenage cost_field.cpp definitions.cpp heuristics.cpp + integration_field.cpp path.cpp tests.cpp types.cpp diff --git a/libopenage/pathfinding/cost_field.cpp b/libopenage/pathfinding/cost_field.cpp index 976c55b3c1..d00f51decf 100644 --- a/libopenage/pathfinding/cost_field.cpp +++ b/libopenage/pathfinding/cost_field.cpp @@ -4,18 +4,28 @@ #include "error/error.h" +#include "pathfinding/definitions.h" + namespace openage::path { CostField::CostField(size_t size) : size{size}, - cells(this->size * this->size, 1) { + cells(this->size * this->size, COST_MIN) { +} + +size_t CostField::get_size() const { + return this->size; } cost_t CostField::get_cost(size_t x, size_t y) const { return this->cells[x + y * this->size]; } +cost_t CostField::get_cost(size_t idx) const { + return this->cells.at(idx); +} + void CostField::set_cost(size_t x, size_t y, cost_t cost) { this->cells[x + y * this->size] = cost; } diff --git a/libopenage/pathfinding/cost_field.h b/libopenage/pathfinding/cost_field.h index 1231517bdd..e9afee27ac 100644 --- a/libopenage/pathfinding/cost_field.h +++ b/libopenage/pathfinding/cost_field.h @@ -22,6 +22,13 @@ class CostField { */ CostField(size_t size); + /** + * Get the size of the cost field. + * + * @return Size of the cost field. + */ + size_t get_size() const; + /** * Get the cost at a specified position. * @@ -31,6 +38,14 @@ class CostField { */ cost_t get_cost(size_t x, size_t y) const; + /** + * Get the cost at a specified position. + * + * @param idx Index of the cell. + * @return Cost at the specified position. + */ + cost_t get_cost(size_t idx) const; + /** * Set the cost at a specified position. * diff --git a/libopenage/pathfinding/definitions.h b/libopenage/pathfinding/definitions.h index 47660a15bb..f90e4c9799 100644 --- a/libopenage/pathfinding/definitions.h +++ b/libopenage/pathfinding/definitions.h @@ -32,6 +32,11 @@ constexpr cost_t COST_MAX = 254; constexpr cost_t COST_IMPASSABLE = 255; +/** + * Start value for goal cells. + */ +const integrate_t INTEGRATE_START = 0; + /** * Unreachable value for a cells in the integration grid. */ diff --git a/libopenage/pathfinding/integration_field.cpp b/libopenage/pathfinding/integration_field.cpp new file mode 100644 index 0000000000..5657136d05 --- /dev/null +++ b/libopenage/pathfinding/integration_field.cpp @@ -0,0 +1,146 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#include "integration_field.h" + +#include +#include + +#include "error/error.h" + +#include "pathfinding/cost_field.h" +#include "pathfinding/definitions.h" + + +namespace openage::path { + +/** + * Update the open list for a neighbour index during integration field + * calculation (subroutine of integrate(..)). + * + * Checks whether: + * + * 1. the neighbour index has not already been found + * 2. the neighbour index is reachable + * + * If both conditions are met, the neighbour index is added to the open list. + * + * @param idx Index of the cell to update. + * @param integrate_cost Integrated cost of the cell. + * @param open_list Cells that still have to be visited. + * @param found Cells that have been found. + */ +void update_list(size_t idx, + integrate_t integrate_cost, + std::deque &open_list, + std::unordered_set &found) { + if (not found.contains(idx)) { + found.insert(idx); + + if (integrate_cost != INTEGRATE_UNREACHABLE) { + open_list.push_back(idx); + } + } +} + + +IntegrationField::IntegrationField(size_t size) : + size{size}, + cells(this->size * this->size, INTEGRATE_UNREACHABLE) { +} + +size_t IntegrationField::get_size() const { + return this->size; +} + +void IntegrationField::integrate(const std::shared_ptr &cost_field, + size_t target_x, + size_t target_y) { + ENSURE(cost_field->get_size() == this->get_size(), + "cost field size " + << cost_field->get_size() << "x" << cost_field->get_size() + << " does not match integration field size " + << this->get_size() << "x" << this->get_size()); + + // Reset the integration field + this->reset(); + + // Target cell index + auto target_idx = target_x + target_y * this->size; + + // Cells that have been found + std::unordered_set found; + found.reserve(this->size * this->size); + // Cells that still have to be visited + std::deque open_list; + + // Move outwards from the target cell, updating the integration field + this->cells[target_idx] = INTEGRATE_START; + open_list.push_back(target_idx); + while (!open_list.empty()) { + auto idx = open_list.front(); + open_list.pop_front(); + + // Get the x and y coordinates of the current cell + auto x = idx % this->size; + auto y = idx / this->size; + + auto integrated_current = this->cells[idx]; + + // Update the integration field of the 4 neighbouring cells + if (y > 0) { + auto up_idx = idx - this->size; + auto neighbor_cost = this->update_cell(up_idx, + cost_field->get_cost(up_idx), + integrated_current); + update_list(up_idx, neighbor_cost, open_list, found); + } + if (x > 0) { + auto left_idx = idx - 1; + auto neighbor_cost = this->update_cell(left_idx, + cost_field->get_cost(left_idx), + integrated_current); + update_list(left_idx, neighbor_cost, open_list, found); + } + if (y < this->size - 1) { + auto down_idx = idx + this->size; + auto neighbor_cost = this->update_cell(down_idx, + cost_field->get_cost(down_idx), + integrated_current); + update_list(down_idx, neighbor_cost, open_list, found); + } + if (x < this->size - 1) { + auto right_idx = idx + 1; + auto neighbor_cost = this->update_cell(right_idx, + cost_field->get_cost(right_idx), + integrated_current); + update_list(right_idx, neighbor_cost, open_list, found); + } + } +} + +void IntegrationField::reset() { + for (auto &cell : this->cells) { + cell = INTEGRATE_UNREACHABLE; + } +} + +integrate_t IntegrationField::update_cell(size_t idx, + cost_t cell_cost, + cost_t integrate_cost) { + ENSURE(cell_cost > COST_INIT, "cost field cell value must be non-zero"); + + // Check if the cell is impassable. + if (cell_cost == COST_IMPASSABLE) { + return INTEGRATE_UNREACHABLE; + } + + // Update the integration field value of the cell. + auto integrated = integrate_cost + cell_cost; + if (integrated < this->cells.at(idx)) { + this->cells[idx] = integrated; + } + + return integrated; +} + +} // namespace openage::path diff --git a/libopenage/pathfinding/integration_field.h b/libopenage/pathfinding/integration_field.h new file mode 100644 index 0000000000..ecb21f5e0b --- /dev/null +++ b/libopenage/pathfinding/integration_field.h @@ -0,0 +1,75 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#pragma once + +#include +#include +#include + +#include "pathfinding/types.h" + + +namespace openage::path { +class CostField; + +/** + * Integration field in the flow-field pathfinding algorithm. + */ +class IntegrationField { +public: + /** + * Create a square integration field with a specified size. + * + * @param size Length of one side of the square field. + */ + IntegrationField(size_t size); + + /** + * Get the size of the integration field. + * + * @return Size of the integration field. + */ + size_t get_size() const; + + /** + * Calculate the integration field for a target cell. + * + * @param cost_field Cost field to integrate. + * @param target_x X coordinate of the target cell. + * @param target_y Y coordinate of the target cell. + */ + void integrate(const std::shared_ptr &cost_field, + size_t target_x, + size_t target_y); + +private: + /** + * Reset the integration field for a new integration. + */ + void reset(); + + /** + * Update a cell in the integration field. + * + * @param idx Index of the cell that is updated. + * @param cell_cost Cost of the cell from the cost field. + * @param integrate_cost Integrated cost of the updating cell in the integration field. + * + * @return New integration value of the cell. + */ + integrate_t update_cell(size_t idx, + cost_t cell_cost, + cost_t integrate_cost); + + /** + * Length of one side of the square integration field. + */ + size_t size; + + /** + * Integration field values. + */ + std::vector cells; +}; + +} // namespace openage::path From 7d14a6adeef71fb82ef45cac3a3469df8986d464 Mon Sep 17 00:00:00 2001 From: heinezen Date: Fri, 9 Feb 2024 23:09:49 +0100 Subject: [PATCH 004/112] path: Flow field value types. --- libopenage/pathfinding/definitions.h | 32 ++++++++++++++++++++++++++++ libopenage/pathfinding/types.h | 8 +++++-- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/libopenage/pathfinding/definitions.h b/libopenage/pathfinding/definitions.h index f90e4c9799..ea8936d809 100644 --- a/libopenage/pathfinding/definitions.h +++ b/libopenage/pathfinding/definitions.h @@ -42,4 +42,36 @@ const integrate_t INTEGRATE_START = 0; */ constexpr integrate_t INTEGRATE_UNREACHABLE = std::numeric_limits::max(); + +/** + * Flow field direction types. + * + * Encoded into the flow_t values. + */ +enum class flow_dir_t : uint8_t { + NORTH = 0x00, + NORTH_EAST = 0x01, + EAST = 0x02, + SOUTH_EAST = 0x03, + SOUTH = 0x04, + SOUTH_WEST = 0x05, + WEST = 0x06, + NORTH_WEST = 0x07, +}; + +/** + * Mask for the flow direction bits in a flow_t value. + */ +constexpr flow_t FLOW_DIR_MASK = 0x0F; + +/** + * Mask for the pathable flag in a flow_t value. + */ +constexpr flow_t FLOW_PATHABLE = 0x10; + +/** + * Mask for the line of sight flag in a flow_t value. + */ +constexpr flow_t FLOW_LOS = 0x20; + } // namespace openage::path diff --git a/libopenage/pathfinding/types.h b/libopenage/pathfinding/types.h index cfc23941ef..d68f8b39ca 100644 --- a/libopenage/pathfinding/types.h +++ b/libopenage/pathfinding/types.h @@ -22,8 +22,12 @@ using cost_t = uint8_t; using integrate_t = uint16_t; /** - * Flow field direction value. + * Flow field cell value. + * + * Bit 2: Line of sight flag. + * Bit 3: Pathable flag. + * Bits 4-7: flow direction. */ -using flow_dir_t = uint8_t; +using flow_t = uint8_t; } // namespace openage::path From c1b6890545a4c445d0defa39645f6d0015cf83ba Mon Sep 17 00:00:00 2001 From: heinezen Date: Fri, 9 Feb 2024 23:10:21 +0100 Subject: [PATCH 005/112] path: Flow field computation. --- libopenage/pathfinding/CMakeLists.txt | 1 + libopenage/pathfinding/cost_field.h | 6 +- libopenage/pathfinding/flow_field.cpp | 115 +++++++++++++++++++ libopenage/pathfinding/flow_field.h | 58 ++++++++++ libopenage/pathfinding/integration_field.cpp | 4 + libopenage/pathfinding/integration_field.h | 11 +- 6 files changed, 190 insertions(+), 5 deletions(-) create mode 100644 libopenage/pathfinding/flow_field.cpp create mode 100644 libopenage/pathfinding/flow_field.h diff --git a/libopenage/pathfinding/CMakeLists.txt b/libopenage/pathfinding/CMakeLists.txt index b40027bdaa..6f943971e3 100644 --- a/libopenage/pathfinding/CMakeLists.txt +++ b/libopenage/pathfinding/CMakeLists.txt @@ -2,6 +2,7 @@ add_sources(libopenage a_star.cpp cost_field.cpp definitions.cpp + flow_field.cpp heuristics.cpp integration_field.cpp path.cpp diff --git a/libopenage/pathfinding/cost_field.h b/libopenage/pathfinding/cost_field.h index e9afee27ac..f256af91ca 100644 --- a/libopenage/pathfinding/cost_field.h +++ b/libopenage/pathfinding/cost_field.h @@ -18,7 +18,7 @@ class CostField { /** * Create a square cost field with a specified size. * - * @param size Length of one side of the square field. + * @param size Side length of the field. */ CostField(size_t size); @@ -71,8 +71,8 @@ class CostField { private: /** - * Length of one side of the square cost field. - */ + * Side length of the field. + */ size_t size; /** diff --git a/libopenage/pathfinding/flow_field.cpp b/libopenage/pathfinding/flow_field.cpp new file mode 100644 index 0000000000..fe67640e2f --- /dev/null +++ b/libopenage/pathfinding/flow_field.cpp @@ -0,0 +1,115 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#include "flow_field.h" + +#include "error/error.h" + +#include "pathfinding/integration_field.h" + + +namespace openage::path { + +FlowField::FlowField(size_t size) : + size{size}, + cells(this->size * this->size, 0) { +} + +FlowField::FlowField(const std::shared_ptr &integrate_field) : + size{integrate_field->get_size()}, + cells(this->size * this->size, 0) { + this->build(integrate_field); +} + +size_t FlowField::get_size() const { + return this->size; +} + +void FlowField::build(const std::shared_ptr &integrate_field) { + ENSURE(integrate_field->get_size() == this->get_size(), + "integration field size " + << integrate_field->get_size() << "x" << integrate_field->get_size() + << " does not match flow field size " + << this->get_size() << "x" << this->get_size()); + + auto &integrate_cells = integrate_field->get_cells(); + auto &flow_cells = this->cells; + + for (size_t y = 0; y < this->size; ++y) { + for (size_t x = 0; x < this->size; ++x) { + size_t idx = y * this->size + x; + + if (integrate_cells[idx] == INTEGRATE_UNREACHABLE) { + // Cell cannot be used as path + continue; + } + + // Find the neighbor with the smallest cost. + flow_dir_t direction; + integrate_t smallest_cost = INTEGRATE_UNREACHABLE; + if (y > 0) { + integrate_t cost = integrate_cells[idx - this->size]; + if (cost < smallest_cost) { + smallest_cost = cost; + direction = flow_dir_t::NORTH; + } + } + if (x < this->size - 1 && y > 0) { + integrate_t cost = integrate_cells[idx - this->size + 1]; + if (cost < smallest_cost) { + smallest_cost = cost; + direction = flow_dir_t::NORTH_EAST; + } + } + if (x < this->size - 1) { + integrate_t cost = integrate_cells[idx + 1]; + if (cost < smallest_cost) { + smallest_cost = cost; + direction = flow_dir_t::EAST; + } + } + if (x < this->size - 1 && y < this->size - 1) { + integrate_t cost = integrate_cells[idx + this->size + 1]; + if (cost < smallest_cost) { + smallest_cost = cost; + direction = flow_dir_t::SOUTH_EAST; + } + } + if (y < this->size - 1) { + integrate_t cost = integrate_cells[idx + this->size]; + if (cost < smallest_cost) { + smallest_cost = cost; + direction = flow_dir_t::SOUTH; + } + } + if (x > 0 && y < this->size - 1) { + integrate_t cost = integrate_cells[idx + this->size - 1]; + if (cost < smallest_cost) { + smallest_cost = cost; + direction = flow_dir_t::SOUTH_WEST; + } + } + if (x > 0) { + integrate_t cost = integrate_cells[idx - 1]; + if (cost < smallest_cost) { + smallest_cost = cost; + direction = flow_dir_t::WEST; + } + } + if (x > 0 && y > 0) { + integrate_t cost = integrate_cells[idx - this->size - 1]; + if (cost < smallest_cost) { + smallest_cost = cost; + direction = flow_dir_t::NORTH_WEST; + } + } + + // Set the flow field cell to pathable. + flow_cells[idx] = flow_cells[idx] | FLOW_PATHABLE; + + // Set the flow field cell to the direction of the smallest cost. + flow_cells[idx] = flow_cells[idx] | static_cast(direction); + } + } +} + +} // namespace openage::path diff --git a/libopenage/pathfinding/flow_field.h b/libopenage/pathfinding/flow_field.h new file mode 100644 index 0000000000..d8a1cacc78 --- /dev/null +++ b/libopenage/pathfinding/flow_field.h @@ -0,0 +1,58 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#pragma once + +#include +#include +#include + +#include "pathfinding/definitions.h" +#include "pathfinding/types.h" + + +namespace openage::path { +class IntegrationField; + +class FlowField { +public: + /** + * Create a square flow field with a specified size. + * + * @param size Side length of the field. + */ + FlowField(size_t size); + + /** + * Create a flow field from an existing integration field. + * + * @param integrate_field Integration field. + */ + FlowField(const std::shared_ptr &integrate_field); + + /** + * Get the size of the flow field. + * + * @return Size of the flow field. + */ + size_t get_size() const; + + /** + * Build the flow field. + * + * @param integrate_field Integration field. + */ + void build(const std::shared_ptr &integrate_field); + +private: + /** + * Side length of the field. + */ + size_t size; + + /** + * Flow field cells. + */ + std::vector cells; +}; + +} // namespace openage::path diff --git a/libopenage/pathfinding/integration_field.cpp b/libopenage/pathfinding/integration_field.cpp index 5657136d05..4e6d59b50d 100644 --- a/libopenage/pathfinding/integration_field.cpp +++ b/libopenage/pathfinding/integration_field.cpp @@ -118,6 +118,10 @@ void IntegrationField::integrate(const std::shared_ptr &cost_field, } } +const std::vector &IntegrationField::get_cells() const { + return this->cells; +} + void IntegrationField::reset() { for (auto &cell : this->cells) { cell = INTEGRATE_UNREACHABLE; diff --git a/libopenage/pathfinding/integration_field.h b/libopenage/pathfinding/integration_field.h index ecb21f5e0b..333b47f528 100644 --- a/libopenage/pathfinding/integration_field.h +++ b/libopenage/pathfinding/integration_field.h @@ -20,7 +20,7 @@ class IntegrationField { /** * Create a square integration field with a specified size. * - * @param size Length of one side of the square field. + * @param size Side length of the field. */ IntegrationField(size_t size); @@ -42,6 +42,13 @@ class IntegrationField { size_t target_x, size_t target_y); + /** + * Get the integration field values. + * + * @return Integration field values. + */ + const std::vector &get_cells() const; + private: /** * Reset the integration field for a new integration. @@ -62,7 +69,7 @@ class IntegrationField { cost_t integrate_cost); /** - * Length of one side of the square integration field. + * Side length of the field. */ size_t size; From a58b631d4bf81b121f1e3d7d5f38c771c4f1f075 Mon Sep 17 00:00:00 2001 From: heinezen Date: Fri, 9 Feb 2024 23:35:24 +0100 Subject: [PATCH 006/112] path: Integrator implementation. --- libopenage/pathfinding/CMakeLists.txt | 1 + libopenage/pathfinding/integrator.cpp | 26 ++++++++++++++++ libopenage/pathfinding/integrator.h | 45 +++++++++++++++++++++++++++ 3 files changed, 72 insertions(+) create mode 100644 libopenage/pathfinding/integrator.cpp create mode 100644 libopenage/pathfinding/integrator.h diff --git a/libopenage/pathfinding/CMakeLists.txt b/libopenage/pathfinding/CMakeLists.txt index 6f943971e3..0a080e694e 100644 --- a/libopenage/pathfinding/CMakeLists.txt +++ b/libopenage/pathfinding/CMakeLists.txt @@ -5,6 +5,7 @@ add_sources(libopenage flow_field.cpp heuristics.cpp integration_field.cpp + integrator.cpp path.cpp tests.cpp types.cpp diff --git a/libopenage/pathfinding/integrator.cpp b/libopenage/pathfinding/integrator.cpp new file mode 100644 index 0000000000..19cd0c408f --- /dev/null +++ b/libopenage/pathfinding/integrator.cpp @@ -0,0 +1,26 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#include "integrator.h" + +#include "pathfinding/cost_field.h" +#include "pathfinding/flow_field.h" +#include "pathfinding/integration_field.h" + + +namespace openage::path { + +void Integrator::set_cost_field(const std::shared_ptr &cost_field) { + this->cost_field = cost_field; +} + +std::shared_ptr Integrator::build(size_t target_x, size_t target_y) { + auto flow_field = std::make_shared(this->cost_field->get_size()); + auto integrate_field = std::make_shared(this->cost_field->get_size()); + + integrate_field->integrate(this->cost_field, target_x, target_y); + flow_field->build(integrate_field); + + return flow_field; +} + +} // namespace openage::path diff --git a/libopenage/pathfinding/integrator.h b/libopenage/pathfinding/integrator.h new file mode 100644 index 0000000000..2d2338982d --- /dev/null +++ b/libopenage/pathfinding/integrator.h @@ -0,0 +1,45 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#pragma once + +#include +#include + + +namespace openage::path { +class CostField; +class FlowField; + +/** + * Integrator for the flow field pathfinding algorithm. + */ +class Integrator { +public: + Integrator() = default; + ~Integrator() = default; + + /** + * Set the cost field. + * + * @param cost_field Cost field. + */ + void set_cost_field(const std::shared_ptr &cost_field); + + /** + * Build the flow field for a target cell. + * + * @param target_x X coordinate of the target cell. + * @param target_y Y coordinate of the target cell. + * + * @return Flow field. + */ + std::shared_ptr build(size_t target_x, size_t target_y); + +private: + /** + * Cost field. + */ + std::shared_ptr cost_field; +}; + +} // namespace openage::path From 5d97ce850b13945898f1b3a8746a6a4562a94580 Mon Sep 17 00:00:00 2001 From: heinezen Date: Fri, 9 Feb 2024 23:44:20 +0100 Subject: [PATCH 007/112] path: Rename old A* cost type to avoid conflicts. --- libopenage/pathfinding/a_star.cpp | 10 +++++----- libopenage/pathfinding/a_star.h | 4 ++-- libopenage/pathfinding/heuristics.cpp | 22 +++++++++++----------- libopenage/pathfinding/heuristics.h | 14 +++++++------- libopenage/pathfinding/path.cpp | 8 ++++---- libopenage/pathfinding/path.h | 16 ++++++++-------- 6 files changed, 37 insertions(+), 37 deletions(-) diff --git a/libopenage/pathfinding/a_star.cpp b/libopenage/pathfinding/a_star.cpp index f80879eb91..6fc4a53c8c 100644 --- a/libopenage/pathfinding/a_star.cpp +++ b/libopenage/pathfinding/a_star.cpp @@ -1,4 +1,4 @@ -// Copyright 2014-2023 the openage authors. See copying.md for legal info. +// Copyright 2014-2024 the openage authors. See copying.md for legal info. /** @file * @@ -31,7 +31,7 @@ Path to_point(coord::phys3 start, auto valid_end = [&](const coord::phys3 &point) -> bool { return euclidean_squared_cost(point, end) < path_grid_size.to_float(); }; - auto heuristic = [&](const coord::phys3 &point) -> cost_t { + auto heuristic = [&](const coord::phys3 &point) -> cost_old_t { return euclidean_cost(point, end); }; return a_star(start, valid_end, heuristic, passable); @@ -41,13 +41,13 @@ Path find_nearest(coord::phys3 start, std::function valid_end, std::function passable) { // Use Dijkstra (heuristic = 0) - auto zero = [](const coord::phys3 &) -> cost_t { return .0f; }; + auto zero = [](const coord::phys3 &) -> cost_old_t { return .0f; }; return a_star(start, valid_end, zero, passable); } Path a_star(coord::phys3 start, std::function valid_end, - std::function heuristic, + std::function heuristic, std::function passable) { // path node storage, always provides cheapest next node. heap_t node_candidates; @@ -94,7 +94,7 @@ Path a_star(coord::phys3 start, } bool not_visited = (visited_tiles.count(neighbor->position) == 0); - cost_t new_past_cost = best_candidate->past_cost + best_candidate->cost_to(*neighbor); + cost_old_t new_past_cost = best_candidate->past_cost + best_candidate->cost_to(*neighbor); // if new cost is better than the previous path if (not_visited or new_past_cost < neighbor->past_cost) { diff --git a/libopenage/pathfinding/a_star.h b/libopenage/pathfinding/a_star.h index 23d3e76747..24e09fee2e 100644 --- a/libopenage/pathfinding/a_star.h +++ b/libopenage/pathfinding/a_star.h @@ -1,4 +1,4 @@ -// Copyright 2014-2023 the openage authors. See copying.md for legal info. +// Copyright 2014-2024 the openage authors. See copying.md for legal info. #pragma once @@ -35,7 +35,7 @@ Path find_nearest(coord::phys3 start, */ Path a_star(coord::phys3 start, std::function valid_end, - std::function heuristic, + std::function heuristic, std::function passable); } // namespace path diff --git a/libopenage/pathfinding/heuristics.cpp b/libopenage/pathfinding/heuristics.cpp index 9a1a1213dc..51a24618a0 100644 --- a/libopenage/pathfinding/heuristics.cpp +++ b/libopenage/pathfinding/heuristics.cpp @@ -1,4 +1,4 @@ -// Copyright 2014-2018 the openage authors. See copying.md for legal info. +// Copyright 2014-2024 the openage authors. See copying.md for legal info. #include "heuristics.h" @@ -9,25 +9,25 @@ namespace openage { namespace path { -cost_t manhattan_cost(const coord::phys3 &start, const coord::phys3 &end) { - cost_t dx = std::abs(start.ne - end.ne).to_float(); - cost_t dy = std::abs(start.se - end.se).to_float(); +cost_old_t manhattan_cost(const coord::phys3 &start, const coord::phys3 &end) { + cost_old_t dx = std::abs(start.ne - end.ne).to_float(); + cost_old_t dy = std::abs(start.se - end.se).to_float(); return dx + dy; } -cost_t chebyshev_cost(const coord::phys3 &start, const coord::phys3 &end) { - cost_t dx = std::abs(start.ne - end.ne).to_float(); - cost_t dy = std::abs(start.se - end.se).to_float(); +cost_old_t chebyshev_cost(const coord::phys3 &start, const coord::phys3 &end) { + cost_old_t dx = std::abs(start.ne - end.ne).to_float(); + cost_old_t dy = std::abs(start.se - end.se).to_float(); return std::max(dx, dy); } -cost_t euclidean_cost(const coord::phys3 &start, const coord::phys3 &end) { +cost_old_t euclidean_cost(const coord::phys3 &start, const coord::phys3 &end) { return (end - start).length(); } -cost_t euclidean_squared_cost(const coord::phys3 &start, const coord::phys3 &end) { - cost_t dx = (start.ne - end.ne).to_float(); - cost_t dy = (start.se - end.se).to_float(); +cost_old_t euclidean_squared_cost(const coord::phys3 &start, const coord::phys3 &end) { + cost_old_t dx = (start.ne - end.ne).to_float(); + cost_old_t dy = (start.se - end.se).to_float(); return dx * dx + dy * dy; } diff --git a/libopenage/pathfinding/heuristics.h b/libopenage/pathfinding/heuristics.h index 7755f1288a..9d84560f4c 100644 --- a/libopenage/pathfinding/heuristics.h +++ b/libopenage/pathfinding/heuristics.h @@ -1,4 +1,4 @@ -// Copyright 2014-2017 the openage authors. See copying.md for legal info. +// Copyright 2014-2024 the openage authors. See copying.md for legal info. #pragma once @@ -10,36 +10,36 @@ namespace path { /** * function pointer type for distance estimation functions. */ -using heuristic_t = cost_t (*)(const coord::phys3 &start, const coord::phys3 &end); +using heuristic_t = cost_old_t (*)(const coord::phys3 &start, const coord::phys3 &end); /** * Manhattan distance cost estimation. * @returns the sum of the x and y difference. */ -cost_t manhattan_cost(const coord::phys3 &start, const coord::phys3 &end); +cost_old_t manhattan_cost(const coord::phys3 &start, const coord::phys3 &end); /** * Chebyshev distance cost estimation. * @returns y or x difference, whichever is higher. */ -cost_t chebyshev_cost(const coord::phys3 &start, const coord::phys3 &end); +cost_old_t chebyshev_cost(const coord::phys3 &start, const coord::phys3 &end); /** * Euclidean distance cost estimation. * @returns the hypotenuse length of the rectangular triangle formed. */ -cost_t euclidean_cost(const coord::phys3 &start, const coord::phys3 &end); +cost_old_t euclidean_cost(const coord::phys3 &start, const coord::phys3 &end); /** * Squared euclidean distance cost estimation. * @returns the square of the hypotenuse length of the rectangular triangle formed. */ -cost_t euclidean_squared_cost(const coord::phys3 &start, const coord::phys3 &end); +cost_old_t euclidean_squared_cost(const coord::phys3 &start, const coord::phys3 &end); /** * Calculate euclidean distance from a already calculated squared euclidean distance */ -cost_t euclidean_squared_to_euclidean_cost(const cost_t euclidean_squared_value); +cost_old_t euclidean_squared_to_euclidean_cost(const cost_old_t euclidean_squared_value); } // namespace path } // namespace openage diff --git a/libopenage/pathfinding/path.cpp b/libopenage/pathfinding/path.cpp index f1da51af78..a709ab0648 100644 --- a/libopenage/pathfinding/path.cpp +++ b/libopenage/pathfinding/path.cpp @@ -1,4 +1,4 @@ -// Copyright 2014-2023 the openage authors. See copying.md for legal info. +// Copyright 2014-2024 the openage authors. See copying.md for legal info. #include @@ -26,13 +26,13 @@ Node::Node(const coord::phys3 &pos, node_pt prev) : this->direction = (this->position - prev->position).normalize(); // TODO: add dot product to coord - cost_t similarity = ((this->direction.ne.to_float() * prev->direction.ne.to_float()) + (this->direction.se.to_float() * prev->direction.se.to_float())); + cost_old_t similarity = ((this->direction.ne.to_float() * prev->direction.ne.to_float()) + (this->direction.se.to_float() * prev->direction.se.to_float())); this->factor += (1 - similarity); } } -Node::Node(const coord::phys3 &pos, node_pt prev, cost_t past, cost_t heuristic) : +Node::Node(const coord::phys3 &pos, node_pt prev, cost_old_t past, cost_old_t heuristic) : Node{pos, prev} { this->past_cost = past; this->heuristic_cost = heuristic; @@ -50,7 +50,7 @@ bool Node::operator==(const Node &other) const { } -cost_t Node::cost_to(const Node &other) const { +cost_old_t Node::cost_to(const Node &other) const { // ignore the up-position, thus convert to phys2 return ((this->position - other.position).to_phys2().length() * other.factor * this->factor); diff --git a/libopenage/pathfinding/path.h b/libopenage/pathfinding/path.h index e41ddbcc28..bbe7aa0f62 100644 --- a/libopenage/pathfinding/path.h +++ b/libopenage/pathfinding/path.h @@ -1,4 +1,4 @@ -// Copyright 2014-2023 the openage authors. See copying.md for legal info. +// Copyright 2014-2024 the openage authors. See copying.md for legal info. #pragma once @@ -24,7 +24,7 @@ class Path; /** * The data type for movement cost */ -using cost_t = float; +using cost_old_t = float; /** * Type for storing navigation nodes. @@ -80,7 +80,7 @@ bool passable_line(node_pt start, node_pt end, std::function { public: Node(const coord::phys3 &pos, node_pt prev); - Node(const coord::phys3 &pos, node_pt prev, cost_t past, cost_t heuristic); + Node(const coord::phys3 &pos, node_pt prev, cost_old_t past, cost_old_t heuristic); /** * Orders nodes according to their future cost value. @@ -96,7 +96,7 @@ class Node : public std::enable_shared_from_this { /** * Calculates the actual movement cose to another node. */ - cost_t cost_to(const Node &other) const; + cost_old_t cost_to(const Node &other) const; /** * Create a backtrace path beginning at this node. @@ -119,20 +119,20 @@ class Node : public std::enable_shared_from_this { /** * Future cost estimation value for this node. */ - cost_t future_cost; + cost_old_t future_cost; /** * Evaluated past cost value for the node. * This stores the actual cost from start to this node. */ - cost_t past_cost; + cost_old_t past_cost; /** * Heuristic cost cache. * Calculated once, is the heuristic distance from this node * to the goal. */ - cost_t heuristic_cost; + cost_old_t heuristic_cost; /** * Can this node be passed? @@ -155,7 +155,7 @@ class Node : public std::enable_shared_from_this { * Factor to adjust movement cost. * default: 1 */ - cost_t factor; + cost_old_t factor; /** * Node where this one was reached by least cost. From 7ddd25d992cf38d210773dc3b00c01cf3b5e36c1 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sat, 10 Feb 2024 00:17:02 +0100 Subject: [PATCH 008/112] path: Test flow field calculations. --- libopenage/pathfinding/flow_field.cpp | 4 +++ libopenage/pathfinding/flow_field.h | 7 ++++ libopenage/pathfinding/tests.cpp | 51 ++++++++++++++++++++++++--- openage/testing/testlist.py | 3 +- 4 files changed, 60 insertions(+), 5 deletions(-) diff --git a/libopenage/pathfinding/flow_field.cpp b/libopenage/pathfinding/flow_field.cpp index fe67640e2f..bd16ec924e 100644 --- a/libopenage/pathfinding/flow_field.cpp +++ b/libopenage/pathfinding/flow_field.cpp @@ -112,4 +112,8 @@ void FlowField::build(const std::shared_ptr &integrate_field) } } +const std::vector &FlowField::get_cells() const { + return this->cells; +} + } // namespace openage::path diff --git a/libopenage/pathfinding/flow_field.h b/libopenage/pathfinding/flow_field.h index d8a1cacc78..03d2806d37 100644 --- a/libopenage/pathfinding/flow_field.h +++ b/libopenage/pathfinding/flow_field.h @@ -43,6 +43,13 @@ class FlowField { */ void build(const std::shared_ptr &integrate_field); + /** + * Get the flow field values. + * + * @return Flow field values. + */ + const std::vector &get_cells() const; + private: /** * Side length of the field. diff --git a/libopenage/pathfinding/tests.cpp b/libopenage/pathfinding/tests.cpp index 35f29295e8..00304237ec 100644 --- a/libopenage/pathfinding/tests.cpp +++ b/libopenage/pathfinding/tests.cpp @@ -1,9 +1,12 @@ -// Copyright 2015-2018 the openage authors. See copying.md for legal info. +// Copyright 2015-2024 the openage authors. See copying.md for legal info. #include "../log/log.h" #include "../testing/testing.h" +#include "cost_field.h" +#include "flow_field.h" #include "heuristics.h" +#include "integrator.h" #include "path.h" namespace openage { @@ -110,7 +113,7 @@ void node_cost_to_0() { TESTEQUALS(n2->cost_to(*n0), 5); // Testing cost_to with both se and ne: - coord::phys3 p3{3, 4, 0}; // -> sqrt(3*3 + 4*4) == 5 + coord::phys3 p3{3, 4, 0}; // -> sqrt(3*3 + 4*4) == 5 node_pt n3 = std::make_unique(p3, nullptr); TESTEQUALS(n0->cost_to(*n3), 5); @@ -274,7 +277,8 @@ bool not_passable(const coord::phys3 &) { bool sometimes_passable(const coord::phys3 &pos) { if (pos.ne == 20) { return false; - } else { + } + else { return true; } } @@ -311,6 +315,45 @@ void path_node() { node_passable_line_0(); } +void flow_field() { + // Create initial cost grid + auto cost_field = std::make_shared(3); + + // | 1 | 1 | 1 | + // | 1 | X | 1 | + // | 1 | 1 | 1 | + cost_field->set_costs({1, 1, 1, 1, 255, 1, 1, 1, 1}); + + // Integrator for managing the flow field + auto integrator = std::make_shared(); + integrator->set_cost_field(cost_field); + + // Build the flow field + auto flow_field = integrator->build(2, 2); + auto cells = flow_field->get_cells(); + + // The directions in the flow field should look like: + // | E | SE | S | + // | SE | X | S | + // | E | E | N | + auto expected = std::vector{ + FLOW_PATHABLE | static_cast(flow_dir_t::EAST), + FLOW_PATHABLE | static_cast(flow_dir_t::SOUTH_EAST), + FLOW_PATHABLE | static_cast(flow_dir_t::SOUTH), + FLOW_PATHABLE | static_cast(flow_dir_t::SOUTH_EAST), + 0, + FLOW_PATHABLE | static_cast(flow_dir_t::SOUTH), + FLOW_PATHABLE | static_cast(flow_dir_t::EAST), + FLOW_PATHABLE | static_cast(flow_dir_t::EAST), + FLOW_PATHABLE | static_cast(flow_dir_t::NORTH), + }; + + for (size_t i = 0; i < cells.size(); i++) { + TESTEQUALS(cells[i], expected[i]); + } +} + + } // namespace tests -} // namespace pathfinding +} // namespace path } // namespace openage diff --git a/openage/testing/testlist.py b/openage/testing/testlist.py index 75a684e85e..2a6a4780fd 100644 --- a/openage/testing/testlist.py +++ b/openage/testing/testlist.py @@ -1,4 +1,4 @@ -# Copyright 2015-2023 the openage authors. See copying.md for legal info. +# Copyright 2015-2024 the openage authors. See copying.md for legal info. """ Lists of all possible tests; enter your tests here. """ @@ -85,6 +85,7 @@ def tests_cpp(): yield "openage::datastructure::tests::pairing_heap" yield "openage::job::tests::test_job_manager" yield "openage::path::tests::path_node", "pathfinding" + yield "openage::path::tests::flow_field", "pathfinding" yield "openage::pyinterface::tests::pyobject" yield "openage::pyinterface::tests::err_py_to_cpp" yield "openage::renderer::tests::font" From a11200993ebb1ff41ac5c8486dbafab1ebb14ec5 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sat, 10 Feb 2024 16:38:43 +0100 Subject: [PATCH 009/112] path: Test individual field types. --- libopenage/pathfinding/tests.cpp | 69 ++++++++++++++++++++++++++------ 1 file changed, 57 insertions(+), 12 deletions(-) diff --git a/libopenage/pathfinding/tests.cpp b/libopenage/pathfinding/tests.cpp index 00304237ec..6e060c4301 100644 --- a/libopenage/pathfinding/tests.cpp +++ b/libopenage/pathfinding/tests.cpp @@ -4,10 +4,13 @@ #include "../testing/testing.h" #include "cost_field.h" +#include "definitions.h" #include "flow_field.h" #include "heuristics.h" +#include "integration_field.h" #include "integrator.h" #include "path.h" +#include "types.h" namespace openage { namespace path { @@ -324,19 +327,11 @@ void flow_field() { // | 1 | 1 | 1 | cost_field->set_costs({1, 1, 1, 1, 255, 1, 1, 1, 1}); - // Integrator for managing the flow field - auto integrator = std::make_shared(); - integrator->set_cost_field(cost_field); - - // Build the flow field - auto flow_field = integrator->build(2, 2); - auto cells = flow_field->get_cells(); - - // The directions in the flow field should look like: + // The flow field for targeting (2, 2) hould look like this: // | E | SE | S | // | SE | X | S | // | E | E | N | - auto expected = std::vector{ + auto ff_expected = std::vector{ FLOW_PATHABLE | static_cast(flow_dir_t::EAST), FLOW_PATHABLE | static_cast(flow_dir_t::SOUTH_EAST), FLOW_PATHABLE | static_cast(flow_dir_t::SOUTH), @@ -348,8 +343,58 @@ void flow_field() { FLOW_PATHABLE | static_cast(flow_dir_t::NORTH), }; - for (size_t i = 0; i < cells.size(); i++) { - TESTEQUALS(cells[i], expected[i]); + // Test the different field types + { + auto integration_field = std::make_shared(3); + integration_field->integrate(cost_field, 2, 2); + auto int_cells = integration_field->get_cells(); + + // The integration field should look like: + // | 4 | 3 | 2 | + // | 3 | X | 1 | + // | 2 | 1 | 0 | + auto int_expected = std::vector{ + 4, + 3, + 2, + 3, + 65535, + 1, + 2, + 1, + 0, + }; + + // Compare the integration field cells with the expected values + for (size_t i = 0; i < int_cells.size(); i++) { + TESTEQUALS(int_cells[i], int_expected[i]); + } + + // Build the flow field + auto flow_field = std::make_shared(3); + flow_field->build(integration_field); + auto ff_cells = flow_field->get_cells(); + + // Compare the flow field cells with the expected values + for (size_t i = 0; i < ff_cells.size(); i++) { + TESTEQUALS(ff_cells[i], ff_expected[i]); + } + } + + // Integrator test + { + // Integrator for managing the flow field + auto integrator = std::make_shared(); + integrator->set_cost_field(cost_field); + + // Build the flow field + auto flow_field = integrator->build(2, 2); + auto ff_cells = flow_field->get_cells(); + + // Compare the flow field cells with the expected values + for (size_t i = 0; i < ff_cells.size(); i++) { + TESTEQUALS(ff_cells[i], ff_expected[i]); + } } } From a1449a4fef150dd8507f40972d3ac134b6cebce6 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sat, 10 Feb 2024 18:02:37 +0100 Subject: [PATCH 010/112] path: Add demo skeleton for flowfield pathfinder. --- libopenage/pathfinding/CMakeLists.txt | 2 + libopenage/pathfinding/demo/CMakeLists.txt | 8 ++++ libopenage/pathfinding/demo/demo_0.cpp | 27 ++++++++++++ libopenage/pathfinding/demo/demo_0.h | 23 ++++++++++ libopenage/pathfinding/demo/tests.cpp | 24 +++++++++++ libopenage/pathfinding/demo/tests.h | 20 +++++++++ openage/CMakeLists.txt | 1 + openage/pathfinding/CMakeLists.txt | 7 ++++ openage/pathfinding/__init__.py | 5 +++ openage/pathfinding/tests.pyx | 49 ++++++++++++++++++++++ openage/testing/testlist.py | 2 + 11 files changed, 168 insertions(+) create mode 100644 libopenage/pathfinding/demo/CMakeLists.txt create mode 100644 libopenage/pathfinding/demo/demo_0.cpp create mode 100644 libopenage/pathfinding/demo/demo_0.h create mode 100644 libopenage/pathfinding/demo/tests.cpp create mode 100644 libopenage/pathfinding/demo/tests.h create mode 100644 openage/pathfinding/CMakeLists.txt create mode 100644 openage/pathfinding/__init__.py create mode 100644 openage/pathfinding/tests.pyx diff --git a/libopenage/pathfinding/CMakeLists.txt b/libopenage/pathfinding/CMakeLists.txt index 0a080e694e..917cae429a 100644 --- a/libopenage/pathfinding/CMakeLists.txt +++ b/libopenage/pathfinding/CMakeLists.txt @@ -10,3 +10,5 @@ add_sources(libopenage tests.cpp types.cpp ) + +add_subdirectory("demo") diff --git a/libopenage/pathfinding/demo/CMakeLists.txt b/libopenage/pathfinding/demo/CMakeLists.txt new file mode 100644 index 0000000000..27ad003577 --- /dev/null +++ b/libopenage/pathfinding/demo/CMakeLists.txt @@ -0,0 +1,8 @@ +add_sources(libopenage + demo_0.cpp + tests.cpp +) + +pxdgen( + tests.h +) diff --git a/libopenage/pathfinding/demo/demo_0.cpp b/libopenage/pathfinding/demo/demo_0.cpp new file mode 100644 index 0000000000..9cd18cf5a1 --- /dev/null +++ b/libopenage/pathfinding/demo/demo_0.cpp @@ -0,0 +1,27 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#include "demo_0.h" + +#include "renderer/gui/integration/public/gui_application_with_logger.h" +#include "renderer/opengl/window.h" + + +namespace openage::path::tests { + +void path_demo_0(const util::Path &path) { + auto qtapp = std::make_shared(); + + renderer::opengl::GlWindow window("openage pathfinding test", 1024, 768, true); + auto renderer = window.make_renderer(); + + while (not window.should_close()) { + qtapp->process_events(); + + window.update(); + + renderer->check_error(); + } + window.close(); +} + +} // namespace openage::path::tests diff --git a/libopenage/pathfinding/demo/demo_0.h b/libopenage/pathfinding/demo/demo_0.h new file mode 100644 index 0000000000..5758b9c33e --- /dev/null +++ b/libopenage/pathfinding/demo/demo_0.h @@ -0,0 +1,23 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#pragma once + +#include "util/path.h" + + +namespace openage::path::tests { + +/** + * Show the pathfinding functionality of the path module: + * - Cost field + * - Integration field + * - Flow field + * + * Visualizes the pathfinding results using our rendering backend. + * + * @param path Path to the project rootdir. + */ +void path_demo_0(const util::Path &path); + + +} // namespace openage::path::tests diff --git a/libopenage/pathfinding/demo/tests.cpp b/libopenage/pathfinding/demo/tests.cpp new file mode 100644 index 0000000000..da6e59479f --- /dev/null +++ b/libopenage/pathfinding/demo/tests.cpp @@ -0,0 +1,24 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#include "tests.h" + +#include "log/log.h" + +#include "pathfinding/demo/demo_0.h" + + +namespace openage::path::tests { + +void path_demo(int demo_id, const util::Path &path) { + switch (demo_id) { + case 0: + path_demo_0(path); + break; + + default: + log::log(MSG(err) << "Unknown pathfinding demo requested: " << demo_id << "."); + break; + } +} + +} // namespace openage::path::tests diff --git a/libopenage/pathfinding/demo/tests.h b/libopenage/pathfinding/demo/tests.h new file mode 100644 index 0000000000..2f411f6bf1 --- /dev/null +++ b/libopenage/pathfinding/demo/tests.h @@ -0,0 +1,20 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#pragma once + +#include "../../util/compiler.h" +// pxd: from libopenage.util.path cimport Path + + +namespace openage { +namespace util { +class Path; +} // namespace util + +namespace path::tests { + +// pxd: void path_demo(int demo_id, Path path) except + +OAAPI void path_demo(int demo_id, const util::Path &path); + +} // namespace path::tests +} // namespace openage diff --git a/openage/CMakeLists.txt b/openage/CMakeLists.txt index a9233357dd..1b31e7110f 100644 --- a/openage/CMakeLists.txt +++ b/openage/CMakeLists.txt @@ -27,6 +27,7 @@ add_subdirectory(gamestate) add_subdirectory(log) add_subdirectory(main) add_subdirectory(nyan) +add_subdirectory(pathfinding) add_subdirectory(renderer) add_subdirectory(testing) add_subdirectory(util) diff --git a/openage/pathfinding/CMakeLists.txt b/openage/pathfinding/CMakeLists.txt new file mode 100644 index 0000000000..03ab1f8e49 --- /dev/null +++ b/openage/pathfinding/CMakeLists.txt @@ -0,0 +1,7 @@ +add_cython_modules( + tests.pyx +) + +add_py_modules( + __init__.py +) diff --git a/openage/pathfinding/__init__.py b/openage/pathfinding/__init__.py new file mode 100644 index 0000000000..8c497dfafb --- /dev/null +++ b/openage/pathfinding/__init__.py @@ -0,0 +1,5 @@ +# Copyright 2024-2024 the openage authors. See copying.md for legal info. + +""" +openage pathfinding system +""" diff --git a/openage/pathfinding/tests.pyx b/openage/pathfinding/tests.pyx new file mode 100644 index 0000000000..f180cb786d --- /dev/null +++ b/openage/pathfinding/tests.pyx @@ -0,0 +1,49 @@ +# Copyright 2024-2024 the openage authors. See copying.md for legal info. + +""" +tests for the pathfinding system. +""" + +import argparse + +from libopenage.util.path cimport Path as Path_cpp +from libopenage.pyinterface.pyobject cimport PyObj +from cpython.ref cimport PyObject +from libopenage.pathfinding.demo.tests cimport path_demo as path_demo_c + +def path_demo(list argv): + """ + invokes the available pathfinding demos. + """ + + cmd = argparse.ArgumentParser( + prog='... path_demo', + description='Demo of the pathfinding system') + cmd.add_argument("test_id", type=int, help="id of the demo to run.") + cmd.add_argument("--asset-dir", + help="Use this as an additional asset directory.") + cmd.add_argument("--cfg-dir", + help="Use this as an additional config directory.") + + args = cmd.parse_args(argv) + + from ..cvar.location import get_config_path + from ..assets import get_asset_path + from ..util.fslike.union import Union + + # create virtual file system for data paths + root = Union().root + + # mount the assets folder union at "assets/" + root["assets"].mount(get_asset_path(args.asset_dir)) + + # mount the config folder at "cfg/" + root["cfg"].mount(get_config_path(args.cfg_dir)) + + cdef int demo_id = args.test_id + + cdef Path_cpp root_cpp = Path_cpp(PyObj(root.fsobj), + root.parts) + + with nogil: + path_demo_c(demo_id, root_cpp) diff --git a/openage/testing/testlist.py b/openage/testing/testlist.py index 2a6a4780fd..37f323e8a8 100644 --- a/openage/testing/testlist.py +++ b/openage/testing/testlist.py @@ -52,6 +52,8 @@ def demos_py(): "play pong on steroids through future prediction") yield ("openage.gamestate.tests.simulation_demo", "showcases the game simulation") + yield ("openage.pathfinding.tests.path_demo", + "showcases the pathfinding system") yield ("openage.renderer.tests.renderer_demo", "showcases the renderer") yield ("openage.renderer.tests.renderer_stresstest", From fc2e9b6a86ffb16383bc4e9f85c7535a9ecebb5c Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 11 Feb 2024 00:24:47 +0100 Subject: [PATCH 011/112] path: Render background in flow field demo. --- .../test/shaders/pathfinding/CMakeLists.txt | 4 + .../pathfinding/demo_0_cost_field.frag.glsl | 1 + .../pathfinding/demo_0_cost_field.vert.glsl | 1 + .../pathfinding/demo_0_display.frag.glsl | 10 ++ .../pathfinding/demo_0_display.vert.glsl | 10 ++ .../pathfinding/demo_0_flow_field.frag.glsl | 1 + .../pathfinding/demo_0_flow_field.vert.glsl | 1 + .../demo_0_integration_field.frag.glsl | 1 + .../demo_0_integration_field.vert.glsl | 1 + .../shaders/pathfinding/demo_0_obj.frag.glsl | 9 ++ .../shaders/pathfinding/demo_0_obj.vert.glsl | 7 + libopenage/pathfinding/demo/demo_0.cpp | 122 ++++++++++++++++++ 12 files changed, 168 insertions(+) create mode 100644 assets/test/shaders/pathfinding/CMakeLists.txt create mode 100644 assets/test/shaders/pathfinding/demo_0_cost_field.frag.glsl create mode 100644 assets/test/shaders/pathfinding/demo_0_cost_field.vert.glsl create mode 100644 assets/test/shaders/pathfinding/demo_0_display.frag.glsl create mode 100644 assets/test/shaders/pathfinding/demo_0_display.vert.glsl create mode 100644 assets/test/shaders/pathfinding/demo_0_flow_field.frag.glsl create mode 100644 assets/test/shaders/pathfinding/demo_0_flow_field.vert.glsl create mode 100644 assets/test/shaders/pathfinding/demo_0_integration_field.frag.glsl create mode 100644 assets/test/shaders/pathfinding/demo_0_integration_field.vert.glsl create mode 100644 assets/test/shaders/pathfinding/demo_0_obj.frag.glsl create mode 100644 assets/test/shaders/pathfinding/demo_0_obj.vert.glsl diff --git a/assets/test/shaders/pathfinding/CMakeLists.txt b/assets/test/shaders/pathfinding/CMakeLists.txt new file mode 100644 index 0000000000..2d30f59426 --- /dev/null +++ b/assets/test/shaders/pathfinding/CMakeLists.txt @@ -0,0 +1,4 @@ +install(DIRECTORY "." + DESTINATION "${ASSET_DIR}/test/shaders/pathfinding" + FILES_MATCHING PATTERN "*.glsl" +) diff --git a/assets/test/shaders/pathfinding/demo_0_cost_field.frag.glsl b/assets/test/shaders/pathfinding/demo_0_cost_field.frag.glsl new file mode 100644 index 0000000000..813598b38b --- /dev/null +++ b/assets/test/shaders/pathfinding/demo_0_cost_field.frag.glsl @@ -0,0 +1 @@ +#version 330 diff --git a/assets/test/shaders/pathfinding/demo_0_cost_field.vert.glsl b/assets/test/shaders/pathfinding/demo_0_cost_field.vert.glsl new file mode 100644 index 0000000000..813598b38b --- /dev/null +++ b/assets/test/shaders/pathfinding/demo_0_cost_field.vert.glsl @@ -0,0 +1 @@ +#version 330 diff --git a/assets/test/shaders/pathfinding/demo_0_display.frag.glsl b/assets/test/shaders/pathfinding/demo_0_display.frag.glsl new file mode 100644 index 0000000000..a6732d0c7f --- /dev/null +++ b/assets/test/shaders/pathfinding/demo_0_display.frag.glsl @@ -0,0 +1,10 @@ +#version 330 + +uniform sampler2D color_texture; + +in vec2 v_uv; +out vec4 col; + +void main() { + col = texture(color_texture, v_uv); +} diff --git a/assets/test/shaders/pathfinding/demo_0_display.vert.glsl b/assets/test/shaders/pathfinding/demo_0_display.vert.glsl new file mode 100644 index 0000000000..6112530242 --- /dev/null +++ b/assets/test/shaders/pathfinding/demo_0_display.vert.glsl @@ -0,0 +1,10 @@ +#version 330 + +layout(location=0) in vec2 position; +layout(location=1) in vec2 uv; +out vec2 v_uv; + +void main() { + gl_Position = vec4(position, 0.0, 1.0); + v_uv = uv; +} diff --git a/assets/test/shaders/pathfinding/demo_0_flow_field.frag.glsl b/assets/test/shaders/pathfinding/demo_0_flow_field.frag.glsl new file mode 100644 index 0000000000..813598b38b --- /dev/null +++ b/assets/test/shaders/pathfinding/demo_0_flow_field.frag.glsl @@ -0,0 +1 @@ +#version 330 diff --git a/assets/test/shaders/pathfinding/demo_0_flow_field.vert.glsl b/assets/test/shaders/pathfinding/demo_0_flow_field.vert.glsl new file mode 100644 index 0000000000..813598b38b --- /dev/null +++ b/assets/test/shaders/pathfinding/demo_0_flow_field.vert.glsl @@ -0,0 +1 @@ +#version 330 diff --git a/assets/test/shaders/pathfinding/demo_0_integration_field.frag.glsl b/assets/test/shaders/pathfinding/demo_0_integration_field.frag.glsl new file mode 100644 index 0000000000..813598b38b --- /dev/null +++ b/assets/test/shaders/pathfinding/demo_0_integration_field.frag.glsl @@ -0,0 +1 @@ +#version 330 diff --git a/assets/test/shaders/pathfinding/demo_0_integration_field.vert.glsl b/assets/test/shaders/pathfinding/demo_0_integration_field.vert.glsl new file mode 100644 index 0000000000..813598b38b --- /dev/null +++ b/assets/test/shaders/pathfinding/demo_0_integration_field.vert.glsl @@ -0,0 +1 @@ +#version 330 diff --git a/assets/test/shaders/pathfinding/demo_0_obj.frag.glsl b/assets/test/shaders/pathfinding/demo_0_obj.frag.glsl new file mode 100644 index 0000000000..ebbb10bc8f --- /dev/null +++ b/assets/test/shaders/pathfinding/demo_0_obj.frag.glsl @@ -0,0 +1,9 @@ +#version 330 + +uniform vec4 color; + +out vec4 outcol; + +void main() { + outcol = color; +} diff --git a/assets/test/shaders/pathfinding/demo_0_obj.vert.glsl b/assets/test/shaders/pathfinding/demo_0_obj.vert.glsl new file mode 100644 index 0000000000..527e0bb652 --- /dev/null +++ b/assets/test/shaders/pathfinding/demo_0_obj.vert.glsl @@ -0,0 +1,7 @@ +#version 330 + +layout(location=0) in vec2 position; + +void main() { + gl_Position = vec4(position, 0.0, 1.0); +} diff --git a/libopenage/pathfinding/demo/demo_0.cpp b/libopenage/pathfinding/demo/demo_0.cpp index 9cd18cf5a1..e0877ff66c 100644 --- a/libopenage/pathfinding/demo/demo_0.cpp +++ b/libopenage/pathfinding/demo/demo_0.cpp @@ -4,6 +4,11 @@ #include "renderer/gui/integration/public/gui_application_with_logger.h" #include "renderer/opengl/window.h" +#include "renderer/renderer.h" +#include "renderer/resources/mesh_data.h" +#include "renderer/resources/shader_source.h" +#include "renderer/resources/texture_info.h" +#include "renderer/shader_program.h" namespace openage::path::tests { @@ -14,9 +19,126 @@ void path_demo_0(const util::Path &path) { renderer::opengl::GlWindow window("openage pathfinding test", 1024, 768, true); auto renderer = window.make_renderer(); + auto shaderdir = path / "assets" / "test" / "shaders" / "pathfinding"; + + /* Shader for rendering the cost field */ + auto cf_vshader_file = shaderdir / "demo_0_cost_field.vert.glsl"; + auto cf_vshader_src = renderer::resources::ShaderSource( + renderer::resources::shader_lang_t::glsl, + renderer::resources::shader_stage_t::vertex, + cf_vshader_file); + + auto cf_fshader_file = shaderdir / "demo_0_cost_field.frag.glsl"; + auto cf_fshader_src = renderer::resources::ShaderSource( + renderer::resources::shader_lang_t::glsl, + renderer::resources::shader_stage_t::fragment, + cf_fshader_file); + + /* Shader for rendering the integration field */ + auto if_vshader_file = shaderdir / "demo_0_integration_field.vert.glsl"; + auto if_vshader_src = renderer::resources::ShaderSource( + renderer::resources::shader_lang_t::glsl, + renderer::resources::shader_stage_t::vertex, + if_vshader_file); + + auto if_fshader_file = shaderdir / "demo_0_integration_field.frag.glsl"; + auto if_fshader_src = renderer::resources::ShaderSource( + renderer::resources::shader_lang_t::glsl, + renderer::resources::shader_stage_t::fragment, + if_fshader_file); + + /* Shader for rendering the flow field */ + auto ff_vshader_file = shaderdir / "demo_0_flow_field.vert.glsl"; + auto ff_vshader_src = renderer::resources::ShaderSource( + renderer::resources::shader_lang_t::glsl, + renderer::resources::shader_stage_t::vertex, + ff_vshader_file); + + auto ff_fshader_file = shaderdir / "demo_0_flow_field.frag.glsl"; + auto ff_fshader_src = renderer::resources::ShaderSource( + renderer::resources::shader_lang_t::glsl, + renderer::resources::shader_stage_t::fragment, + ff_fshader_file); + + /* Shader for monocolored objects. */ + auto obj_vshader_file = shaderdir / "demo_0_obj.vert.glsl"; + auto obj_vshader_src = renderer::resources::ShaderSource( + renderer::resources::shader_lang_t::glsl, + renderer::resources::shader_stage_t::vertex, + obj_vshader_file); + + auto obj_fshader_file = shaderdir / "demo_0_obj.frag.glsl"; + auto obj_fshader_src = renderer::resources::ShaderSource( + renderer::resources::shader_lang_t::glsl, + renderer::resources::shader_stage_t::fragment, + obj_fshader_file); + + /* Shader for rendering to the display target */ + auto display_vshader_file = shaderdir / "demo_0_display.vert.glsl"; + auto display_vshader_src = renderer::resources::ShaderSource( + renderer::resources::shader_lang_t::glsl, + renderer::resources::shader_stage_t::vertex, + display_vshader_file); + + auto display_fshader_file = shaderdir / "demo_0_display.frag.glsl"; + auto display_fshader_src = renderer::resources::ShaderSource( + renderer::resources::shader_lang_t::glsl, + renderer::resources::shader_stage_t::fragment, + display_fshader_file); + + // Create the shaders + auto cf_shader = renderer->add_shader({cf_vshader_src, cf_fshader_src}); + auto if_shader = renderer->add_shader({if_vshader_src, if_fshader_src}); + auto ff_shader = renderer->add_shader({ff_vshader_src, ff_fshader_src}); + auto obj_shader = renderer->add_shader({obj_vshader_src, obj_fshader_src}); + auto display_shader = renderer->add_shader({display_vshader_src, display_fshader_src}); + + /* Make a framebuffer for the field render pass to draw into. */ + auto size = window.get_size(); + auto color_texture = renderer->add_texture( + renderer::resources::Texture2dInfo(size[0], + size[1], + renderer::resources::pixel_format::rgba8)); + auto depth_texture = renderer->add_texture( + renderer::resources::Texture2dInfo(size[0], + size[1], + renderer::resources::pixel_format::depth24)); + auto fbo = renderer->create_texture_target({color_texture, depth_texture}); + + auto obj_pass = renderer->add_render_pass({}, fbo); + + /* Make an object encompassing the entire screen for the display render pass */ + auto color_texture_unif = display_shader->new_uniform_input("color_texture", color_texture); + auto quad = renderer->add_mesh_geometry(renderer::resources::MeshData::make_quad()); + renderer::Renderable display_obj{ + color_texture_unif, + quad, + false, + false, + }; + + auto display_pass = renderer->add_render_pass({display_obj}, renderer->get_display_target()); + + + auto background_unifs = obj_shader->new_uniform_input( + "color", + Eigen::Vector4f{64.0 / 256, 128.0 / 256, 196.0 / 256, 1.0}); + auto background = renderer->add_mesh_geometry(renderer::resources::MeshData::make_quad()); + renderer::Renderable background_obj{ + background_unifs, + background, + false, + false, + }; + obj_pass->add_renderables(background_obj); + + while (not window.should_close()) { qtapp->process_events(); + renderer->render(obj_pass); + renderer->render(display_pass); + window.update(); renderer->check_error(); From e17d0529a35ab69287ce7666d8c016f02a087014 Mon Sep 17 00:00:00 2001 From: heinezen Date: Tue, 13 Feb 2024 18:08:24 +0100 Subject: [PATCH 012/112] path: Convert indent in multi-line comments to tabs. --- libopenage/pathfinding/cost_field.h | 70 +++++++++++----------- libopenage/pathfinding/flow_field.h | 44 +++++++------- libopenage/pathfinding/integration_field.h | 8 +-- libopenage/pathfinding/integrator.h | 26 ++++---- 4 files changed, 74 insertions(+), 74 deletions(-) diff --git a/libopenage/pathfinding/cost_field.h b/libopenage/pathfinding/cost_field.h index f256af91ca..b4f5cfbb00 100644 --- a/libopenage/pathfinding/cost_field.h +++ b/libopenage/pathfinding/cost_field.h @@ -16,57 +16,57 @@ namespace openage::path { class CostField { public: /** - * Create a square cost field with a specified size. - * - * @param size Side length of the field. - */ + * Create a square cost field with a specified size. + * + * @param size Side length of the field. + */ CostField(size_t size); /** - * Get the size of the cost field. - * - * @return Size of the cost field. - */ + * Get the size of the cost field. + * + * @return Size of the cost field. + */ size_t get_size() const; /** - * Get the cost at a specified position. - * - * @param x X coordinate. - * @param y Y coordinate. - * @return Cost at the specified position. - */ + * Get the cost at a specified position. + * + * @param x X coordinate. + * @param y Y coordinate. + * @return Cost at the specified position. + */ cost_t get_cost(size_t x, size_t y) const; /** - * Get the cost at a specified position. - * - * @param idx Index of the cell. - * @return Cost at the specified position. - */ + * Get the cost at a specified position. + * + * @param idx Index of the cell. + * @return Cost at the specified position. + */ cost_t get_cost(size_t idx) const; /** - * Set the cost at a specified position. - * - * @param x X coordinate. - * @param y Y coordinate. - * @param cost Cost to set. - */ + * Set the cost at a specified position. + * + * @param x X coordinate. + * @param y Y coordinate. + * @param cost Cost to set. + */ void set_cost(size_t x, size_t y, cost_t cost); /** - * Get the cost field values. - * - * @return Cost field values. - */ + * Get the cost field values. + * + * @return Cost field values. + */ const std::vector &get_costs() const; /** - * Set the cost field values. - * - * @param cells Cost field values. - */ + * Set the cost field values. + * + * @param cells Cost field values. + */ void set_costs(std::vector &&cells); private: @@ -76,8 +76,8 @@ class CostField { size_t size; /** - * Cost field values. - */ + * Cost field values. + */ std::vector cells; }; diff --git a/libopenage/pathfinding/flow_field.h b/libopenage/pathfinding/flow_field.h index 03d2806d37..8b09e06017 100644 --- a/libopenage/pathfinding/flow_field.h +++ b/libopenage/pathfinding/flow_field.h @@ -16,38 +16,38 @@ class IntegrationField; class FlowField { public: /** - * Create a square flow field with a specified size. - * - * @param size Side length of the field. - */ + * Create a square flow field with a specified size. + * + * @param size Side length of the field. + */ FlowField(size_t size); /** - * Create a flow field from an existing integration field. - * - * @param integrate_field Integration field. - */ + * Create a flow field from an existing integration field. + * + * @param integrate_field Integration field. + */ FlowField(const std::shared_ptr &integrate_field); /** - * Get the size of the flow field. - * - * @return Size of the flow field. - */ + * Get the size of the flow field. + * + * @return Size of the flow field. + */ size_t get_size() const; /** - * Build the flow field. - * - * @param integrate_field Integration field. - */ + * Build the flow field. + * + * @param integrate_field Integration field. + */ void build(const std::shared_ptr &integrate_field); /** - * Get the flow field values. - * - * @return Flow field values. - */ + * Get the flow field values. + * + * @return Flow field values. + */ const std::vector &get_cells() const; private: @@ -57,8 +57,8 @@ class FlowField { size_t size; /** - * Flow field cells. - */ + * Flow field cells. + */ std::vector cells; }; diff --git a/libopenage/pathfinding/integration_field.h b/libopenage/pathfinding/integration_field.h index 333b47f528..ad116c942e 100644 --- a/libopenage/pathfinding/integration_field.h +++ b/libopenage/pathfinding/integration_field.h @@ -43,10 +43,10 @@ class IntegrationField { size_t target_y); /** - * Get the integration field values. - * - * @return Integration field values. - */ + * Get the integration field values. + * + * @return Integration field values. + */ const std::vector &get_cells() const; private: diff --git a/libopenage/pathfinding/integrator.h b/libopenage/pathfinding/integrator.h index 2d2338982d..a00eaa2700 100644 --- a/libopenage/pathfinding/integrator.h +++ b/libopenage/pathfinding/integrator.h @@ -19,26 +19,26 @@ class Integrator { ~Integrator() = default; /** - * Set the cost field. - * - * @param cost_field Cost field. - */ + * Set the cost field. + * + * @param cost_field Cost field. + */ void set_cost_field(const std::shared_ptr &cost_field); /** - * Build the flow field for a target cell. - * - * @param target_x X coordinate of the target cell. - * @param target_y Y coordinate of the target cell. - * - * @return Flow field. - */ + * Build the flow field for a target cell. + * + * @param target_x X coordinate of the target cell. + * @param target_y Y coordinate of the target cell. + * + * @return Flow field. + */ std::shared_ptr build(size_t target_x, size_t target_y); private: /** - * Cost field. - */ + * Cost field. + */ std::shared_ptr cost_field; }; From 8f0242a00b09df2be5dd4275b66f83252a8b1720 Mon Sep 17 00:00:00 2001 From: heinezen Date: Tue, 13 Feb 2024 23:25:04 +0100 Subject: [PATCH 013/112] path: Draw cost field in demo. --- .../pathfinding/demo_0_cost_field.frag.glsl | 12 ++ .../pathfinding/demo_0_cost_field.vert.glsl | 14 ++ libopenage/pathfinding/demo/demo_0.cpp | 159 +++++++++++++++--- 3 files changed, 163 insertions(+), 22 deletions(-) diff --git a/assets/test/shaders/pathfinding/demo_0_cost_field.frag.glsl b/assets/test/shaders/pathfinding/demo_0_cost_field.frag.glsl index 813598b38b..26fe7ef44e 100644 --- a/assets/test/shaders/pathfinding/demo_0_cost_field.frag.glsl +++ b/assets/test/shaders/pathfinding/demo_0_cost_field.frag.glsl @@ -1 +1,13 @@ #version 330 + +in float v_cost; + +out vec4 out_col; + +void main() +{ + float cost = v_cost * 2.0; + float red = clamp(cost, 0.0, 1.0); + float green = clamp(2.0 - cost, 0.0, 1.0); + out_col = vec4(red, green, 0.0, 1.0); +} diff --git a/assets/test/shaders/pathfinding/demo_0_cost_field.vert.glsl b/assets/test/shaders/pathfinding/demo_0_cost_field.vert.glsl index 813598b38b..cf76408fe5 100644 --- a/assets/test/shaders/pathfinding/demo_0_cost_field.vert.glsl +++ b/assets/test/shaders/pathfinding/demo_0_cost_field.vert.glsl @@ -1 +1,15 @@ #version 330 + +layout (location = 0) in vec3 position; +layout (location = 1) in float cost; + +uniform mat4 model; +uniform mat4 view; +uniform mat4 proj; + +out float v_cost; + +void main() { + gl_Position = proj * view * model * vec4(position, 1.0); + v_cost = cost / 256.0; +} diff --git a/libopenage/pathfinding/demo/demo_0.cpp b/libopenage/pathfinding/demo/demo_0.cpp index e0877ff66c..240b3c559a 100644 --- a/libopenage/pathfinding/demo/demo_0.cpp +++ b/libopenage/pathfinding/demo/demo_0.cpp @@ -2,6 +2,10 @@ #include "demo_0.h" +#include "pathfinding/cost_field.h" +#include "pathfinding/flow_field.h" +#include "pathfinding/integration_field.h" +#include "renderer/camera/camera.h" #include "renderer/gui/integration/public/gui_application_with_logger.h" #include "renderer/opengl/window.h" #include "renderer/renderer.h" @@ -9,19 +13,87 @@ #include "renderer/resources/shader_source.h" #include "renderer/resources/texture_info.h" #include "renderer/shader_program.h" +#include "util/vector.h" namespace openage::path::tests { +renderer::resources::MeshData get_cost_field_mesh(const std::shared_ptr &field) { + // increase by 1 in every dimension because to get the vertex length + // of each dimension + util::Vector2s size{field->get_size() + 1, field->get_size() + 1}; + + // add vertices for the cells of the grid + std::vector verts{}; + auto vert_count = size[0] * size[1]; + verts.reserve(vert_count * 3); + for (int i = 0; i < (int)size[0]; ++i) { + for (int j = 0; j < (int)size[1]; ++j) { + coord::scene3 v{ + static_cast(i), + static_cast(j), + 0, + }; + auto world_v = v.to_world_space(); + verts.push_back(world_v[0]); + verts.push_back(world_v[1]); + verts.push_back(world_v[2]); + verts.push_back(1.0); // TODO: push back actual cost + } + } + + // split the grid into triangles using an index array + std::vector idxs; + idxs.reserve((size[0] - 1) * (size[1] - 1) * 6); + // iterate over all tiles in the grid by columns, i.e. starting + // from the left corner to the bottom corner if you imagine it from + // the camera's point of view + for (size_t i = 0; i < size[0] - 1; ++i) { + for (size_t j = 0; j < size[1] - 1; ++j) { + // since we are working on tiles, we split each tile into two triangles + // with counter-clockwise vertex order + idxs.push_back(j + i * size[1]); // bottom left + idxs.push_back(j + 1 + i * size[1]); // bottom right + idxs.push_back(j + size[1] + i * size[1]); // top left + idxs.push_back(j + 1 + i * size[1]); // bottom right + idxs.push_back(j + size[1] + 1 + i * size[1]); // top right + idxs.push_back(j + size[1] + i * size[1]); // top left + } + } + + renderer::resources::VertexInputInfo info{ + {renderer::resources::vertex_input_t::V3F32, renderer::resources::vertex_input_t::F32}, + renderer::resources::vertex_layout_t::AOS, + renderer::resources::vertex_primitive_t::TRIANGLES, + renderer::resources::index_t::U16}; + + auto const vert_data_size = verts.size() * sizeof(float); + std::vector vert_data(vert_data_size); + std::memcpy(vert_data.data(), verts.data(), vert_data_size); + + auto const idx_data_size = idxs.size() * sizeof(uint16_t); + std::vector idx_data(idx_data_size); + std::memcpy(idx_data.data(), idxs.data(), idx_data_size); + + return {std::move(vert_data), std::move(idx_data), info}; +} + void path_demo_0(const util::Path &path) { auto qtapp = std::make_shared(); renderer::opengl::GlWindow window("openage pathfinding test", 1024, 768, true); auto renderer = window.make_renderer(); + // Camera for correct projection of terrain + auto camera = std::make_shared(renderer, window.get_size()); + window.add_resize_callback([&](size_t w, size_t h, double /*scale*/) { + camera->resize(w, h); + }); + + // Shader sources auto shaderdir = path / "assets" / "test" / "shaders" / "pathfinding"; - /* Shader for rendering the cost field */ + // Shader for rendering the cost field auto cf_vshader_file = shaderdir / "demo_0_cost_field.vert.glsl"; auto cf_vshader_src = renderer::resources::ShaderSource( renderer::resources::shader_lang_t::glsl, @@ -34,7 +106,7 @@ void path_demo_0(const util::Path &path) { renderer::resources::shader_stage_t::fragment, cf_fshader_file); - /* Shader for rendering the integration field */ + // Shader for rendering the integration field auto if_vshader_file = shaderdir / "demo_0_integration_field.vert.glsl"; auto if_vshader_src = renderer::resources::ShaderSource( renderer::resources::shader_lang_t::glsl, @@ -47,7 +119,7 @@ void path_demo_0(const util::Path &path) { renderer::resources::shader_stage_t::fragment, if_fshader_file); - /* Shader for rendering the flow field */ + // Shader for rendering the flow field auto ff_vshader_file = shaderdir / "demo_0_flow_field.vert.glsl"; auto ff_vshader_src = renderer::resources::ShaderSource( renderer::resources::shader_lang_t::glsl, @@ -60,7 +132,7 @@ void path_demo_0(const util::Path &path) { renderer::resources::shader_stage_t::fragment, ff_fshader_file); - /* Shader for monocolored objects. */ + // Shader for monocolored objects auto obj_vshader_file = shaderdir / "demo_0_obj.vert.glsl"; auto obj_vshader_src = renderer::resources::ShaderSource( renderer::resources::shader_lang_t::glsl, @@ -73,7 +145,7 @@ void path_demo_0(const util::Path &path) { renderer::resources::shader_stage_t::fragment, obj_fshader_file); - /* Shader for rendering to the display target */ + // Shader for rendering to the display target auto display_vshader_file = shaderdir / "demo_0_display.vert.glsl"; auto display_vshader_src = renderer::resources::ShaderSource( renderer::resources::shader_lang_t::glsl, @@ -93,9 +165,9 @@ void path_demo_0(const util::Path &path) { auto obj_shader = renderer->add_shader({obj_vshader_src, obj_fshader_src}); auto display_shader = renderer->add_shader({display_vshader_src, display_fshader_src}); - /* Make a framebuffer for the field render pass to draw into. */ + // Make a framebuffer for the background render pass to draw into auto size = window.get_size(); - auto color_texture = renderer->add_texture( + auto background_texture = renderer->add_texture( renderer::resources::Texture2dInfo(size[0], size[1], renderer::resources::pixel_format::rgba8)); @@ -103,23 +175,41 @@ void path_demo_0(const util::Path &path) { renderer::resources::Texture2dInfo(size[0], size[1], renderer::resources::pixel_format::depth24)); - auto fbo = renderer->create_texture_target({color_texture, depth_texture}); + auto fbo = renderer->create_texture_target({background_texture, depth_texture}); + auto background_pass = renderer->add_render_pass({}, fbo); - auto obj_pass = renderer->add_render_pass({}, fbo); + // Make a framebuffer for the field render passes to draw into + auto field_texture = renderer->add_texture( + renderer::resources::Texture2dInfo(size[0], + size[1], + renderer::resources::pixel_format::rgba8)); + auto depth_texture_2 = renderer->add_texture( + renderer::resources::Texture2dInfo(size[0], + size[1], + renderer::resources::pixel_format::depth24)); + auto field_fbo = renderer->create_texture_target({field_texture, depth_texture_2}); + auto field_pass = renderer->add_render_pass({}, field_fbo); - /* Make an object encompassing the entire screen for the display render pass */ - auto color_texture_unif = display_shader->new_uniform_input("color_texture", color_texture); + // Make two objects that draw the results of the previous passes onto the screen + // in the display render pass + auto bg_texture_unif = display_shader->new_uniform_input("color_texture", background_texture); auto quad = renderer->add_mesh_geometry(renderer::resources::MeshData::make_quad()); - renderer::Renderable display_obj{ - color_texture_unif, + renderer::Renderable bg_pass_obj{ + bg_texture_unif, quad, - false, - false, + true, + true, }; + auto field_texture_unif = display_shader->new_uniform_input("color_texture", field_texture); + renderer::Renderable field_pass_obj{ + field_texture_unif, + quad, + true, + true, + }; + auto display_pass = renderer->add_render_pass({bg_pass_obj, field_pass_obj}, renderer->get_display_target()); - auto display_pass = renderer->add_render_pass({display_obj}, renderer->get_display_target()); - - + // Background object for contrast between field and display auto background_unifs = obj_shader->new_uniform_input( "color", Eigen::Vector4f{64.0 / 256, 128.0 / 256, 196.0 / 256, 1.0}); @@ -127,16 +217,41 @@ void path_demo_0(const util::Path &path) { renderer::Renderable background_obj{ background_unifs, background, - false, - false, + true, + true, }; - obj_pass->add_renderables(background_obj); + background_pass->add_renderables(background_obj); + // Create the pathfinding fields + auto cost_field = std::make_shared(10); + auto integration_field = std::make_shared(10); + auto flow_field = std::make_shared(10); + + // Create object for the cost field + Eigen::Matrix4f model = Eigen::Matrix4f::Identity(); + auto cost_field_unifs = cf_shader->new_uniform_input( + "model", + model, + "view", + camera->get_view_matrix(), + "proj", + camera->get_projection_matrix()); + auto cost_field_mesh = get_cost_field_mesh(cost_field); + auto cost_field_geometry = renderer->add_mesh_geometry(cost_field_mesh); + // auto cost_field_geometry = renderer->add_mesh_geometry(renderer::resources::MeshData::make_quad()); + renderer::Renderable cost_field_renderable{ + cost_field_unifs, + cost_field_geometry, + true, + true, + }; + field_pass->add_renderables(cost_field_renderable); while (not window.should_close()) { qtapp->process_events(); - renderer->render(obj_pass); + renderer->render(background_pass); + renderer->render(field_pass); renderer->render(display_pass); window.update(); From 51f09aea22179193deef3c26659a8c1da052e741 Mon Sep 17 00:00:00 2001 From: heinezen Date: Wed, 14 Feb 2024 00:33:03 +0100 Subject: [PATCH 014/112] path: Move camera in dem so that whole grid is visible. --- libopenage/pathfinding/demo/demo_0.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libopenage/pathfinding/demo/demo_0.cpp b/libopenage/pathfinding/demo/demo_0.cpp index 240b3c559a..ea6436c1a7 100644 --- a/libopenage/pathfinding/demo/demo_0.cpp +++ b/libopenage/pathfinding/demo/demo_0.cpp @@ -81,11 +81,12 @@ renderer::resources::MeshData get_cost_field_mesh(const std::shared_ptr(); - renderer::opengl::GlWindow window("openage pathfinding test", 1024, 768, true); + renderer::opengl::GlWindow window("openage pathfinding test", 1440, 720, true); auto renderer = window.make_renderer(); // Camera for correct projection of terrain auto camera = std::make_shared(renderer, window.get_size()); + camera->look_at_coord({5, 5, 0}); window.add_resize_callback([&](size_t w, size_t h, double /*scale*/) { camera->resize(w, h); }); From a6840b44548641297593d2bb78a6dcdb6a05fe9d Mon Sep 17 00:00:00 2001 From: heinezen Date: Thu, 15 Feb 2024 20:12:03 +0100 Subject: [PATCH 015/112] path: Draw grid lines on top of field. --- .../shaders/pathfinding/demo_0_grid.frag.glsl | 8 ++ .../shaders/pathfinding/demo_0_grid.vert.glsl | 11 ++ libopenage/pathfinding/demo/demo_0.cpp | 118 +++++++++++++++++- 3 files changed, 135 insertions(+), 2 deletions(-) create mode 100644 assets/test/shaders/pathfinding/demo_0_grid.frag.glsl create mode 100644 assets/test/shaders/pathfinding/demo_0_grid.vert.glsl diff --git a/assets/test/shaders/pathfinding/demo_0_grid.frag.glsl b/assets/test/shaders/pathfinding/demo_0_grid.frag.glsl new file mode 100644 index 0000000000..19d2e459c3 --- /dev/null +++ b/assets/test/shaders/pathfinding/demo_0_grid.frag.glsl @@ -0,0 +1,8 @@ +#version 330 + +out vec4 out_col; + +void main() +{ + out_col = vec4(0.0, 0.0, 0.0, 0.3); +} diff --git a/assets/test/shaders/pathfinding/demo_0_grid.vert.glsl b/assets/test/shaders/pathfinding/demo_0_grid.vert.glsl new file mode 100644 index 0000000000..2f5eaeaa15 --- /dev/null +++ b/assets/test/shaders/pathfinding/demo_0_grid.vert.glsl @@ -0,0 +1,11 @@ +#version 330 + +layout (location = 0) in vec3 position; + +uniform mat4 model; +uniform mat4 view; +uniform mat4 proj; + +void main() { + gl_Position = proj * view * model * vec4(position, 1.0); +} diff --git a/libopenage/pathfinding/demo/demo_0.cpp b/libopenage/pathfinding/demo/demo_0.cpp index ea6436c1a7..f70ad0439e 100644 --- a/libopenage/pathfinding/demo/demo_0.cpp +++ b/libopenage/pathfinding/demo/demo_0.cpp @@ -26,7 +26,7 @@ renderer::resources::MeshData get_cost_field_mesh(const std::shared_ptr verts{}; auto vert_count = size[0] * size[1]; - verts.reserve(vert_count * 3); + verts.reserve(vert_count * 4); for (int i = 0; i < (int)size[0]; ++i) { for (int j = 0; j < (int)size[1]; ++j) { coord::scene3 v{ @@ -78,6 +78,66 @@ renderer::resources::MeshData get_cost_field_mesh(const std::shared_ptr verts{}; + auto vert_count = size[0] * size[1]; + verts.reserve(vert_count * 3); + for (int i = 0; i < (int)size[0]; ++i) { + for (int j = 0; j < (int)size[1]; ++j) { + coord::scene3 v{ + static_cast(i), + static_cast(j), + 0, + }; + auto world_v = v.to_world_space(); + verts.push_back(world_v[0]); + verts.push_back(world_v[1]); + verts.push_back(world_v[2]); + } + } + + // split the grid into lines using an index array + std::vector idxs; + idxs.reserve((size[0] - 1) * (size[1] - 1) * 8); + // iterate over all tiles in the grid by columns, i.e. starting + // from the left corner to the bottom corner if you imagine it from + // the camera's point of view + for (size_t i = 0; i < size[0] - 1; ++i) { + for (size_t j = 0; j < size[1] - 1; ++j) { + // since we are working on tiles, we just draw a square using the 4 vertices + idxs.push_back(j + i * size[1]); // bottom left + idxs.push_back(j + 1 + i * size[1]); // bottom right + idxs.push_back(j + 1 + i * size[1]); // bottom right + idxs.push_back(j + size[1] + 1 + i * size[1]); // top right + idxs.push_back(j + size[1] + 1 + i * size[1]); // top right + idxs.push_back(j + size[1] + i * size[1]); // top left + idxs.push_back(j + size[1] + i * size[1]); // top left + idxs.push_back(j + i * size[1]); // bottom left + } + } + + renderer::resources::VertexInputInfo info{ + {renderer::resources::vertex_input_t::V3F32}, + renderer::resources::vertex_layout_t::AOS, + renderer::resources::vertex_primitive_t::LINES, + renderer::resources::index_t::U16}; + + auto const vert_data_size = verts.size() * sizeof(float); + std::vector vert_data(vert_data_size); + std::memcpy(vert_data.data(), verts.data(), vert_data_size); + + auto const idx_data_size = idxs.size() * sizeof(uint16_t); + std::vector idx_data(idx_data_size); + std::memcpy(idx_data.data(), idxs.data(), idx_data_size); + + return {std::move(vert_data), std::move(idx_data), info}; +} + void path_demo_0(const util::Path &path) { auto qtapp = std::make_shared(); @@ -133,6 +193,19 @@ void path_demo_0(const util::Path &path) { renderer::resources::shader_stage_t::fragment, ff_fshader_file); + // Shader for rendering the grid + auto grid_vshader_file = shaderdir / "demo_0_grid.vert.glsl"; + auto grid_vshader_src = renderer::resources::ShaderSource( + renderer::resources::shader_lang_t::glsl, + renderer::resources::shader_stage_t::vertex, + grid_vshader_file); + + auto grid_fshader_file = shaderdir / "demo_0_grid.frag.glsl"; + auto grid_fshader_src = renderer::resources::ShaderSource( + renderer::resources::shader_lang_t::glsl, + renderer::resources::shader_stage_t::fragment, + grid_fshader_file); + // Shader for monocolored objects auto obj_vshader_file = shaderdir / "demo_0_obj.vert.glsl"; auto obj_vshader_src = renderer::resources::ShaderSource( @@ -163,6 +236,7 @@ void path_demo_0(const util::Path &path) { auto cf_shader = renderer->add_shader({cf_vshader_src, cf_fshader_src}); auto if_shader = renderer->add_shader({if_vshader_src, if_fshader_src}); auto ff_shader = renderer->add_shader({ff_vshader_src, ff_fshader_src}); + auto grid_shader = renderer->add_shader({grid_vshader_src, grid_fshader_src}); auto obj_shader = renderer->add_shader({obj_vshader_src, obj_fshader_src}); auto display_shader = renderer->add_shader({display_vshader_src, display_fshader_src}); @@ -191,6 +265,18 @@ void path_demo_0(const util::Path &path) { auto field_fbo = renderer->create_texture_target({field_texture, depth_texture_2}); auto field_pass = renderer->add_render_pass({}, field_fbo); + // Make a framebuffer for the grid render passes to draw into + auto grid_texture = renderer->add_texture( + renderer::resources::Texture2dInfo(size[0], + size[1], + renderer::resources::pixel_format::rgba8)); + auto depth_texture_3 = renderer->add_texture( + renderer::resources::Texture2dInfo(size[0], + size[1], + renderer::resources::pixel_format::depth24)); + auto grid_fbo = renderer->create_texture_target({grid_texture, depth_texture_3}); + auto grid_pass = renderer->add_render_pass({}, grid_fbo); + // Make two objects that draw the results of the previous passes onto the screen // in the display render pass auto bg_texture_unif = display_shader->new_uniform_input("color_texture", background_texture); @@ -208,7 +294,16 @@ void path_demo_0(const util::Path &path) { true, true, }; - auto display_pass = renderer->add_render_pass({bg_pass_obj, field_pass_obj}, renderer->get_display_target()); + auto grid_texture_unif = display_shader->new_uniform_input("color_texture", grid_texture); + renderer::Renderable grid_pass_obj{ + grid_texture_unif, + quad, + true, + true, + }; + auto display_pass = renderer->add_render_pass( + {bg_pass_obj, field_pass_obj, grid_pass_obj}, + renderer->get_display_target()); // Background object for contrast between field and display auto background_unifs = obj_shader->new_uniform_input( @@ -248,11 +343,30 @@ void path_demo_0(const util::Path &path) { }; field_pass->add_renderables(cost_field_renderable); + // Create object for the grid + auto grid_unifs = grid_shader->new_uniform_input( + "model", + model, + "view", + camera->get_view_matrix(), + "proj", + camera->get_projection_matrix()); + auto grid_mesh = get_grid_mesh(cost_field->get_size()); + auto grid_geometry = renderer->add_mesh_geometry(grid_mesh); + renderer::Renderable grid_renderable{ + grid_unifs, + grid_geometry, + true, + true, + }; + grid_pass->add_renderables(grid_renderable); + while (not window.should_close()) { qtapp->process_events(); renderer->render(background_pass); renderer->render(field_pass); + renderer->render(grid_pass); renderer->render(display_pass); window.update(); From 29e49ffb1b7e42d2799059cc511c250f5338bf45 Mon Sep 17 00:00:00 2001 From: heinezen Date: Thu, 15 Feb 2024 20:47:46 +0100 Subject: [PATCH 016/112] renderer: Fix unindexed mesh being drawn with wrong primitive. --- libopenage/renderer/opengl/geometry.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libopenage/renderer/opengl/geometry.cpp b/libopenage/renderer/opengl/geometry.cpp index a01b4183af..0e2c930a2a 100644 --- a/libopenage/renderer/opengl/geometry.cpp +++ b/libopenage/renderer/opengl/geometry.cpp @@ -1,4 +1,4 @@ -// Copyright 2015-2023 the openage authors. See copying.md for legal info. +// Copyright 2015-2024 the openage authors. See copying.md for legal info. #include "geometry.h" @@ -68,7 +68,7 @@ void GlGeometry::draw() const { glDrawElements(mesh.primitive, mesh.vert_count, *mesh.index_type, nullptr); } else { - glDrawArrays(GL_TRIANGLE_STRIP, 0, mesh.vert_count); + glDrawArrays(mesh.primitive, 0, mesh.vert_count); } break; From 02e666b44b2a3ab414527bec767f8f9cbacc5d67 Mon Sep 17 00:00:00 2001 From: heinezen Date: Thu, 15 Feb 2024 20:56:10 +0100 Subject: [PATCH 017/112] path: Show cost as tile colours on grid. --- .../pathfinding/demo_0_cost_field.frag.glsl | 6 +- .../pathfinding/demo_0_cost_field.vert.glsl | 2 +- libopenage/pathfinding/demo/demo_0.cpp | 55 ++++++++++++++++--- 3 files changed, 53 insertions(+), 10 deletions(-) diff --git a/assets/test/shaders/pathfinding/demo_0_cost_field.frag.glsl b/assets/test/shaders/pathfinding/demo_0_cost_field.frag.glsl index 26fe7ef44e..7ccb86dcf4 100644 --- a/assets/test/shaders/pathfinding/demo_0_cost_field.frag.glsl +++ b/assets/test/shaders/pathfinding/demo_0_cost_field.frag.glsl @@ -6,7 +6,11 @@ out vec4 out_col; void main() { - float cost = v_cost * 2.0; + if (v_cost == 255.0) { + out_col = vec4(0.0, 0.0, 0.0, 1.0); + return; + } + float cost = (v_cost / 256) * 2.0; float red = clamp(cost, 0.0, 1.0); float green = clamp(2.0 - cost, 0.0, 1.0); out_col = vec4(red, green, 0.0, 1.0); diff --git a/assets/test/shaders/pathfinding/demo_0_cost_field.vert.glsl b/assets/test/shaders/pathfinding/demo_0_cost_field.vert.glsl index cf76408fe5..d12f0db1d9 100644 --- a/assets/test/shaders/pathfinding/demo_0_cost_field.vert.glsl +++ b/assets/test/shaders/pathfinding/demo_0_cost_field.vert.glsl @@ -11,5 +11,5 @@ out float v_cost; void main() { gl_Position = proj * view * model * vec4(position, 1.0); - v_cost = cost / 256.0; + v_cost = cost; } diff --git a/libopenage/pathfinding/demo/demo_0.cpp b/libopenage/pathfinding/demo/demo_0.cpp index f70ad0439e..54493c77b6 100644 --- a/libopenage/pathfinding/demo/demo_0.cpp +++ b/libopenage/pathfinding/demo/demo_0.cpp @@ -18,27 +18,62 @@ namespace openage::path::tests { -renderer::resources::MeshData get_cost_field_mesh(const std::shared_ptr &field) { +/** + * Create a mesh for the cost field. + * + * @param field Cost field to visualize. + * @param resolution Determines the number of subdivisions per grid cell. The number of + * quads per cell is resolution^2. (default = 2) + * + * @return Mesh data for the cost field. + */ +renderer::resources::MeshData get_cost_field_mesh(const std::shared_ptr &field, + size_t resolution = 2) { // increase by 1 in every dimension because to get the vertex length // of each dimension - util::Vector2s size{field->get_size() + 1, field->get_size() + 1}; + util::Vector2s size{ + field->get_size() * resolution + 1, + field->get_size() * resolution + 1, + }; + auto vert_distance = 1.0f / resolution; // add vertices for the cells of the grid std::vector verts{}; auto vert_count = size[0] * size[1]; verts.reserve(vert_count * 4); - for (int i = 0; i < (int)size[0]; ++i) { - for (int j = 0; j < (int)size[1]; ++j) { + for (int i = 0; i < static_cast(size[0]); ++i) { + for (int j = 0; j < static_cast(size[1]); ++j) { + // for each vertex, compare the surrounding tiles + std::vector surround{}; + if (j - 1 >= 0 and i - 1 >= 0) { + auto cost = field->get_cost((i - 1) / resolution, (j - 1) / resolution); + surround.push_back(cost); + } + if (j < static_cast(field->get_size()) and i - 1 >= 0) { + auto cost = field->get_cost((i - 1) / resolution, j / resolution); + surround.push_back(cost); + } + if (j < static_cast(field->get_size()) and i < static_cast(field->get_size())) { + auto cost = field->get_cost(i / resolution, j / resolution); + surround.push_back(cost); + } + if (j - 1 >= 0 and i < static_cast(field->get_size())) { + auto cost = field->get_cost(i / resolution, (j - 1) / resolution); + surround.push_back(cost); + } + // use the cost of the most expensive surrounding tile + auto max_cost = *std::max_element(surround.begin(), surround.end()); + coord::scene3 v{ - static_cast(i), - static_cast(j), + static_cast(i * vert_distance), + static_cast(j * vert_distance), 0, }; auto world_v = v.to_world_space(); verts.push_back(world_v[0]); verts.push_back(world_v[1]); verts.push_back(world_v[2]); - verts.push_back(1.0); // TODO: push back actual cost + verts.push_back(max_cost); // TODO: push back actual cost } } @@ -320,6 +355,10 @@ void path_demo_0(const util::Path &path) { // Create the pathfinding fields auto cost_field = std::make_shared(10); + cost_field->set_cost(0, 0, 255); + cost_field->set_cost(1, 0, 254); + cost_field->set_cost(4, 3, 128); + auto integration_field = std::make_shared(10); auto flow_field = std::make_shared(10); @@ -351,7 +390,7 @@ void path_demo_0(const util::Path &path) { camera->get_view_matrix(), "proj", camera->get_projection_matrix()); - auto grid_mesh = get_grid_mesh(cost_field->get_size()); + auto grid_mesh = get_grid_mesh(10); auto grid_geometry = renderer->add_mesh_geometry(grid_mesh); renderer::Renderable grid_renderable{ grid_unifs, From 3cb498398a4326a2e4ee3ab61ebec29f7b53455b Mon Sep 17 00:00:00 2001 From: heinezen Date: Fri, 16 Feb 2024 01:15:33 +0100 Subject: [PATCH 018/112] path: Draw integration field in demo. --- .../demo_0_integration_field.frag.glsl | 15 +++ .../demo_0_integration_field.vert.glsl | 14 +++ libopenage/pathfinding/demo/demo_0.cpp | 114 +++++++++++++++++- libopenage/pathfinding/integration_field.cpp | 8 ++ libopenage/pathfinding/integration_field.h | 17 +++ 5 files changed, 167 insertions(+), 1 deletion(-) diff --git a/assets/test/shaders/pathfinding/demo_0_integration_field.frag.glsl b/assets/test/shaders/pathfinding/demo_0_integration_field.frag.glsl index 813598b38b..d40715a338 100644 --- a/assets/test/shaders/pathfinding/demo_0_integration_field.frag.glsl +++ b/assets/test/shaders/pathfinding/demo_0_integration_field.frag.glsl @@ -1 +1,16 @@ #version 330 + +in float v_cost; + +out vec4 out_col; + +void main() +{ + if (v_cost > 512.0) { + out_col = vec4(0.0, 0.0, 0.0, 1.0); + return; + } + float cost = 0.05 * v_cost; + float green = clamp(1.0 - cost, 0.0, 1.0); + out_col = vec4(0.75, green, 0.5, 1.0); +} diff --git a/assets/test/shaders/pathfinding/demo_0_integration_field.vert.glsl b/assets/test/shaders/pathfinding/demo_0_integration_field.vert.glsl index 813598b38b..d12f0db1d9 100644 --- a/assets/test/shaders/pathfinding/demo_0_integration_field.vert.glsl +++ b/assets/test/shaders/pathfinding/demo_0_integration_field.vert.glsl @@ -1 +1,15 @@ #version 330 + +layout (location = 0) in vec3 position; +layout (location = 1) in float cost; + +uniform mat4 model; +uniform mat4 view; +uniform mat4 proj; + +out float v_cost; + +void main() { + gl_Position = proj * view * model * vec4(position, 1.0); + v_cost = cost; +} diff --git a/libopenage/pathfinding/demo/demo_0.cpp b/libopenage/pathfinding/demo/demo_0.cpp index 54493c77b6..2f3ccd8243 100644 --- a/libopenage/pathfinding/demo/demo_0.cpp +++ b/libopenage/pathfinding/demo/demo_0.cpp @@ -73,7 +73,7 @@ renderer::resources::MeshData get_cost_field_mesh(const std::shared_ptr &field, + size_t resolution = 2) { + // increase by 1 in every dimension because to get the vertex length + // of each dimension + util::Vector2s size{ + field->get_size() * resolution + 1, + field->get_size() * resolution + 1, + }; + auto vert_distance = 1.0f / resolution; + + // add vertices for the cells of the grid + std::vector verts{}; + auto vert_count = size[0] * size[1]; + verts.reserve(vert_count * 4); + for (int i = 0; i < static_cast(size[0]); ++i) { + for (int j = 0; j < static_cast(size[1]); ++j) { + // for each vertex, compare the surrounding tiles + std::vector surround{}; + if (j - 1 >= 0 and i - 1 >= 0) { + auto cost = field->get_cell((i - 1) / resolution, (j - 1) / resolution); + surround.push_back(cost); + } + if (j < static_cast(field->get_size()) and i - 1 >= 0) { + auto cost = field->get_cell((i - 1) / resolution, j / resolution); + surround.push_back(cost); + } + if (j < static_cast(field->get_size()) and i < static_cast(field->get_size())) { + auto cost = field->get_cell(i / resolution, j / resolution); + surround.push_back(cost); + } + if (j - 1 >= 0 and i < static_cast(field->get_size())) { + auto cost = field->get_cell(i / resolution, (j - 1) / resolution); + surround.push_back(cost); + } + // use the cost of the most expensive surrounding tile + auto max_cost = *std::max_element(surround.begin(), surround.end()); + + coord::scene3 v{ + static_cast(i * vert_distance), + static_cast(j * vert_distance), + 0, + }; + auto world_v = v.to_world_space(); + verts.push_back(world_v[0]); + verts.push_back(world_v[1]); + verts.push_back(world_v[2]); + verts.push_back(max_cost); + } + } + + // split the grid into triangles using an index array + std::vector idxs; + idxs.reserve((size[0] - 1) * (size[1] - 1) * 6); + // iterate over all tiles in the grid by columns, i.e. starting + // from the left corner to the bottom corner if you imagine it from + // the camera's point of view + for (size_t i = 0; i < size[0] - 1; ++i) { + for (size_t j = 0; j < size[1] - 1; ++j) { + // since we are working on tiles, we split each tile into two triangles + // with counter-clockwise vertex order + idxs.push_back(j + i * size[1]); // bottom left + idxs.push_back(j + 1 + i * size[1]); // bottom right + idxs.push_back(j + size[1] + i * size[1]); // top left + idxs.push_back(j + 1 + i * size[1]); // bottom right + idxs.push_back(j + size[1] + 1 + i * size[1]); // top right + idxs.push_back(j + size[1] + i * size[1]); // top left + } + } + + renderer::resources::VertexInputInfo info{ + {renderer::resources::vertex_input_t::V3F32, renderer::resources::vertex_input_t::F32}, + renderer::resources::vertex_layout_t::AOS, + renderer::resources::vertex_primitive_t::TRIANGLES, + renderer::resources::index_t::U16}; + + auto const vert_data_size = verts.size() * sizeof(float); + std::vector vert_data(vert_data_size); + std::memcpy(vert_data.data(), verts.data(), vert_data_size); + + auto const idx_data_size = idxs.size() * sizeof(uint16_t); + std::vector idx_data(idx_data_size); + std::memcpy(idx_data.data(), idxs.data(), idx_data_size); + + return {std::move(vert_data), std::move(idx_data), info}; +} + +/** + * Create a mesh for the grid. + * + * @param side_length Length of the grid's side. + * + * @return Mesh data for the grid. + */ renderer::resources::MeshData get_grid_mesh(size_t side_length) { // increase by 1 in every dimension because to get the vertex length // of each dimension @@ -360,6 +453,7 @@ void path_demo_0(const util::Path &path) { cost_field->set_cost(4, 3, 128); auto integration_field = std::make_shared(10); + integration_field->integrate(cost_field, 7, 7); auto flow_field = std::make_shared(10); // Create object for the cost field @@ -382,6 +476,24 @@ void path_demo_0(const util::Path &path) { }; field_pass->add_renderables(cost_field_renderable); + // Create object for the integration field + auto integration_field_unifs = if_shader->new_uniform_input( + "model", + model, + "view", + camera->get_view_matrix(), + "proj", + camera->get_projection_matrix()); + auto integration_field_mesh = get_integration_field_mesh(integration_field); + auto integration_field_geometry = renderer->add_mesh_geometry(integration_field_mesh); + renderer::Renderable integration_field_renderable{ + integration_field_unifs, + integration_field_geometry, + true, + true, + }; + field_pass->add_renderables(integration_field_renderable); + // Create object for the grid auto grid_unifs = grid_shader->new_uniform_input( "model", diff --git a/libopenage/pathfinding/integration_field.cpp b/libopenage/pathfinding/integration_field.cpp index 4e6d59b50d..695e8340ce 100644 --- a/libopenage/pathfinding/integration_field.cpp +++ b/libopenage/pathfinding/integration_field.cpp @@ -52,6 +52,14 @@ size_t IntegrationField::get_size() const { return this->size; } +integrate_t IntegrationField::get_cell(size_t x, size_t y) const { + return this->cells.at(x + y * this->size); +} + +integrate_t IntegrationField::get_cell(size_t idy) const { + return this->cells.at(idy); +} + void IntegrationField::integrate(const std::shared_ptr &cost_field, size_t target_x, size_t target_y) { diff --git a/libopenage/pathfinding/integration_field.h b/libopenage/pathfinding/integration_field.h index ad116c942e..75356daa7c 100644 --- a/libopenage/pathfinding/integration_field.h +++ b/libopenage/pathfinding/integration_field.h @@ -31,6 +31,23 @@ class IntegrationField { */ size_t get_size() const; + /** + * Get the integration value at a specified position. + * + * @param x X coordinate. + * @param y Y coordinate. + * @return Integration value at the specified position. + */ + integrate_t get_cell(size_t x, size_t y) const; + + /** + * Get the integration value at a specified position. + * + * @param idx Index of the cell. + * @return Integration value at the specified position. + */ + integrate_t get_cell(size_t idy) const; + /** * Calculate the integration field for a target cell. * From 2dfde71eb62dd6a2e782bff014c630d18e2ea03e Mon Sep 17 00:00:00 2001 From: heinezen Date: Sat, 17 Feb 2024 23:48:52 +0100 Subject: [PATCH 019/112] path: Fix method argument name. --- libopenage/pathfinding/integration_field.cpp | 4 ++-- libopenage/pathfinding/integration_field.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/libopenage/pathfinding/integration_field.cpp b/libopenage/pathfinding/integration_field.cpp index 695e8340ce..da4014092a 100644 --- a/libopenage/pathfinding/integration_field.cpp +++ b/libopenage/pathfinding/integration_field.cpp @@ -56,8 +56,8 @@ integrate_t IntegrationField::get_cell(size_t x, size_t y) const { return this->cells.at(x + y * this->size); } -integrate_t IntegrationField::get_cell(size_t idy) const { - return this->cells.at(idy); +integrate_t IntegrationField::get_cell(size_t idx) const { + return this->cells.at(idx); } void IntegrationField::integrate(const std::shared_ptr &cost_field, diff --git a/libopenage/pathfinding/integration_field.h b/libopenage/pathfinding/integration_field.h index 75356daa7c..0f0ad16661 100644 --- a/libopenage/pathfinding/integration_field.h +++ b/libopenage/pathfinding/integration_field.h @@ -46,7 +46,7 @@ class IntegrationField { * @param idx Index of the cell. * @return Integration value at the specified position. */ - integrate_t get_cell(size_t idy) const; + integrate_t get_cell(size_t idx) const; /** * Calculate the integration field for a target cell. From c885c6e0bd03645ee359545c628a5f3459873420 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 18 Feb 2024 01:21:36 +0100 Subject: [PATCH 020/112] path: Fix integration update overflow. --- libopenage/pathfinding/integration_field.cpp | 2 +- libopenage/pathfinding/integration_field.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libopenage/pathfinding/integration_field.cpp b/libopenage/pathfinding/integration_field.cpp index da4014092a..ee0a91d96f 100644 --- a/libopenage/pathfinding/integration_field.cpp +++ b/libopenage/pathfinding/integration_field.cpp @@ -138,7 +138,7 @@ void IntegrationField::reset() { integrate_t IntegrationField::update_cell(size_t idx, cost_t cell_cost, - cost_t integrate_cost) { + integrate_t integrate_cost) { ENSURE(cell_cost > COST_INIT, "cost field cell value must be non-zero"); // Check if the cell is impassable. diff --git a/libopenage/pathfinding/integration_field.h b/libopenage/pathfinding/integration_field.h index 0f0ad16661..fc83683a30 100644 --- a/libopenage/pathfinding/integration_field.h +++ b/libopenage/pathfinding/integration_field.h @@ -83,7 +83,7 @@ class IntegrationField { */ integrate_t update_cell(size_t idx, cost_t cell_cost, - cost_t integrate_cost); + integrate_t integrate_cost); /** * Side length of the field. From 520c9cf9f72bb97830db84241e7a150263a848d0 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 18 Feb 2024 01:22:04 +0100 Subject: [PATCH 021/112] patg: Get individual flow field cell values. --- libopenage/pathfinding/flow_field.cpp | 8 ++++++++ libopenage/pathfinding/flow_field.h | 20 ++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/libopenage/pathfinding/flow_field.cpp b/libopenage/pathfinding/flow_field.cpp index bd16ec924e..27e6e14560 100644 --- a/libopenage/pathfinding/flow_field.cpp +++ b/libopenage/pathfinding/flow_field.cpp @@ -24,6 +24,14 @@ size_t FlowField::get_size() const { return this->size; } +flow_t FlowField::get_cell(size_t x, size_t y) const { + return this->cells.at(x + y * this->size); +} + +flow_dir_t FlowField::get_dir(size_t x, size_t y) const { + return static_cast(this->get_cell(x, y) & FLOW_DIR_MASK); +} + void FlowField::build(const std::shared_ptr &integrate_field) { ENSURE(integrate_field->get_size() == this->get_size(), "integration field size " diff --git a/libopenage/pathfinding/flow_field.h b/libopenage/pathfinding/flow_field.h index 8b09e06017..7fc3f00470 100644 --- a/libopenage/pathfinding/flow_field.h +++ b/libopenage/pathfinding/flow_field.h @@ -36,6 +36,26 @@ class FlowField { */ size_t get_size() const; + /** + * Get the flow field value at a specified position. + * + * @param x X coordinate. + * @param y Y coordinate. + * + * @return Flowfield value at the specified position. + */ + flow_t get_cell(size_t x, size_t y) const; + + /** + * Get the flow field direction at a specified position. + * + * @param x X coordinate. + * @param y Y coordinate. + * + * @return Flowfield direction at the specified position. + */ + flow_dir_t get_dir(size_t x, size_t y) const; + /** * Build the flow field. * From 01bff57955b6a0519cf24ce0f4509fd82495f9de Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 18 Feb 2024 01:25:28 +0100 Subject: [PATCH 022/112] path: Draw flow field in demo. --- .../pathfinding/demo_0_flow_field.frag.glsl | 8 + .../pathfinding/demo_0_flow_field.vert.glsl | 10 + libopenage/pathfinding/demo/demo_0.cpp | 207 +++++++++++++++++- 3 files changed, 222 insertions(+), 3 deletions(-) diff --git a/assets/test/shaders/pathfinding/demo_0_flow_field.frag.glsl b/assets/test/shaders/pathfinding/demo_0_flow_field.frag.glsl index 813598b38b..ebbb10bc8f 100644 --- a/assets/test/shaders/pathfinding/demo_0_flow_field.frag.glsl +++ b/assets/test/shaders/pathfinding/demo_0_flow_field.frag.glsl @@ -1 +1,9 @@ #version 330 + +uniform vec4 color; + +out vec4 outcol; + +void main() { + outcol = color; +} diff --git a/assets/test/shaders/pathfinding/demo_0_flow_field.vert.glsl b/assets/test/shaders/pathfinding/demo_0_flow_field.vert.glsl index 813598b38b..8b6b015408 100644 --- a/assets/test/shaders/pathfinding/demo_0_flow_field.vert.glsl +++ b/assets/test/shaders/pathfinding/demo_0_flow_field.vert.glsl @@ -1 +1,11 @@ #version 330 + +layout(location=0) in vec3 position; + +uniform mat4 model; +uniform mat4 view; +uniform mat4 proj; + +void main() { + gl_Position = proj * view * model * vec4(position, 1.0); +} diff --git a/libopenage/pathfinding/demo/demo_0.cpp b/libopenage/pathfinding/demo/demo_0.cpp index 2f3ccd8243..2e03a508b3 100644 --- a/libopenage/pathfinding/demo/demo_0.cpp +++ b/libopenage/pathfinding/demo/demo_0.cpp @@ -13,6 +13,7 @@ #include "renderer/resources/shader_source.h" #include "renderer/resources/texture_info.h" #include "renderer/shader_program.h" +#include "util/math_constants.h" #include "util/vector.h" @@ -113,6 +114,16 @@ renderer::resources::MeshData get_cost_field_mesh(const std::shared_ptr &field, size_t resolution = 2) { // increase by 1 in every dimension because to get the vertex length @@ -199,6 +210,127 @@ renderer::resources::MeshData get_integration_field_mesh(const std::shared_ptr &field, + size_t resolution = 2) { + // increase by 1 in every dimension because to get the vertex length + // of each dimension + util::Vector2s size{ + field->get_size() * resolution + 1, + field->get_size() * resolution + 1, + }; + auto vert_distance = 1.0f / resolution; + + // add vertices for the cells of the grid + std::vector verts{}; + auto vert_count = size[0] * size[1]; + verts.reserve(vert_count * 4); + for (int i = 0; i < static_cast(size[0]); ++i) { + for (int j = 0; j < static_cast(size[1]); ++j) { + // for each vertex, compare the surrounding tiles + std::vector surround{}; + if (j - 1 >= 0 and i - 1 >= 0) { + auto cost = field->get_cell((i - 1) / resolution, (j - 1) / resolution); + surround.push_back(cost); + } + if (j < static_cast(field->get_size()) and i - 1 >= 0) { + auto cost = field->get_cell((i - 1) / resolution, j / resolution); + surround.push_back(cost); + } + if (j < static_cast(field->get_size()) and i < static_cast(field->get_size())) { + auto cost = field->get_cell(i / resolution, j / resolution); + surround.push_back(cost); + } + if (j - 1 >= 0 and i < static_cast(field->get_size())) { + auto cost = field->get_cell(i / resolution, (j - 1) / resolution); + surround.push_back(cost); + } + // use the cost of the most expensive surrounding tile + auto max_cost = *std::max_element(surround.begin(), surround.end()); + + coord::scene3 v{ + static_cast(i * vert_distance), + static_cast(j * vert_distance), + 0, + }; + auto world_v = v.to_world_space(); + verts.push_back(world_v[0]); + verts.push_back(world_v[1]); + verts.push_back(world_v[2]); + verts.push_back(max_cost); + } + } + + // split the grid into triangles using an index array + std::vector idxs; + idxs.reserve((size[0] - 1) * (size[1] - 1) * 6); + // iterate over all tiles in the grid by columns, i.e. starting + // from the left corner to the bottom corner if you imagine it from + // the camera's point of view + for (size_t i = 0; i < size[0] - 1; ++i) { + for (size_t j = 0; j < size[1] - 1; ++j) { + // since we are working on tiles, we split each tile into two triangles + // with counter-clockwise vertex order + idxs.push_back(j + i * size[1]); // bottom left + idxs.push_back(j + 1 + i * size[1]); // bottom right + idxs.push_back(j + size[1] + i * size[1]); // top left + idxs.push_back(j + 1 + i * size[1]); // bottom right + idxs.push_back(j + size[1] + 1 + i * size[1]); // top right + idxs.push_back(j + size[1] + i * size[1]); // top left + } + } + + renderer::resources::VertexInputInfo info{ + {renderer::resources::vertex_input_t::V3F32, renderer::resources::vertex_input_t::F32}, + renderer::resources::vertex_layout_t::AOS, + renderer::resources::vertex_primitive_t::TRIANGLES, + renderer::resources::index_t::U16}; + + auto const vert_data_size = verts.size() * sizeof(float); + std::vector vert_data(vert_data_size); + std::memcpy(vert_data.data(), verts.data(), vert_data_size); + + auto const idx_data_size = idxs.size() * sizeof(uint16_t); + std::vector idx_data(idx_data_size); + std::memcpy(idx_data.data(), idxs.data(), idx_data_size); + + return {std::move(vert_data), std::move(idx_data), info}; +} + +/** + * Create a mesh for an arrow. + * + * @return Mesh data for an arrow. + */ +renderer::resources::MeshData get_arrow_mesh() { + // vertices for the arrow + // x, y, z + std::vector verts{ + // clang-format off + 0.2f, 0.01f, -0.05f, + 0.2f, 0.01f, 0.05f, + -0.2f, 0.01f, -0.05f, + -0.2f, 0.01f, 0.05f, + // clang-format on + }; + + renderer::resources::VertexInputInfo info{ + {renderer::resources::vertex_input_t::V3F32}, + renderer::resources::vertex_layout_t::AOS, + renderer::resources::vertex_primitive_t::TRIANGLE_STRIP}; + + auto const vert_data_size = verts.size() * sizeof(float); + std::vector vert_data(vert_data_size); + std::memcpy(vert_data.data(), verts.data(), vert_data_size); + + return {std::move(vert_data), info}; +} + + /** * Create a mesh for the grid. * @@ -451,10 +583,16 @@ void path_demo_0(const util::Path &path) { cost_field->set_cost(0, 0, 255); cost_field->set_cost(1, 0, 254); cost_field->set_cost(4, 3, 128); + cost_field->set_cost(5, 3, 128); + cost_field->set_cost(6, 3, 128); + cost_field->set_cost(4, 4, 128); + cost_field->set_cost(5, 4, 128); + cost_field->set_cost(6, 4, 128); auto integration_field = std::make_shared(10); integration_field->integrate(cost_field, 7, 7); auto flow_field = std::make_shared(10); + flow_field->build(integration_field); // Create object for the cost field Eigen::Matrix4f model = Eigen::Matrix4f::Identity(); @@ -467,14 +605,13 @@ void path_demo_0(const util::Path &path) { camera->get_projection_matrix()); auto cost_field_mesh = get_cost_field_mesh(cost_field); auto cost_field_geometry = renderer->add_mesh_geometry(cost_field_mesh); - // auto cost_field_geometry = renderer->add_mesh_geometry(renderer::resources::MeshData::make_quad()); renderer::Renderable cost_field_renderable{ cost_field_unifs, cost_field_geometry, true, true, }; - field_pass->add_renderables(cost_field_renderable); + // field_pass->add_renderables(cost_field_renderable); // Create object for the integration field auto integration_field_unifs = if_shader->new_uniform_input( @@ -484,7 +621,7 @@ void path_demo_0(const util::Path &path) { camera->get_view_matrix(), "proj", camera->get_projection_matrix()); - auto integration_field_mesh = get_integration_field_mesh(integration_field); + auto integration_field_mesh = get_integration_field_mesh(integration_field, 4); auto integration_field_geometry = renderer->add_mesh_geometry(integration_field_mesh); renderer::Renderable integration_field_renderable{ integration_field_unifs, @@ -494,6 +631,70 @@ void path_demo_0(const util::Path &path) { }; field_pass->add_renderables(integration_field_renderable); + // Create object for the flow field + auto flow_field_unifs = ff_shader->new_uniform_input( + "model", + model, + "view", + camera->get_view_matrix(), + "proj", + camera->get_projection_matrix(), + "color", + Eigen::Vector4f{192.0 / 256, 255.0 / 256, 64.0 / 256, 1.0}); + auto flow_field_mesh = get_flow_field_mesh(flow_field); + auto flow_field_geometry = renderer->add_mesh_geometry(flow_field_mesh); + renderer::Renderable flow_field_renderable{ + flow_field_unifs, + flow_field_geometry, + true, + true, + }; + // field_pass->add_renderables(flow_field_renderable); + + std::unordered_map offset_dir{ + {flow_dir_t::NORTH, {-0.25f, 0.0f, 0.0f}}, + {flow_dir_t::NORTH_EAST, {-0.25f, 0.0f, -0.25f}}, + {flow_dir_t::EAST, {0.0f, 0.0f, -0.25f}}, + {flow_dir_t::SOUTH_EAST, {0.25f, 0.0f, -0.25f}}, + {flow_dir_t::SOUTH, {0.25f, 0.0f, 0.0f}}, + {flow_dir_t::SOUTH_WEST, {0.25f, 0.0f, 0.25f}}, + {flow_dir_t::WEST, {0.0f, 0.0f, 0.25f}}, + {flow_dir_t::NORTH_WEST, {-0.25f, 0.0f, 0.25f}}, + }; + + for (size_t y = 0; y < flow_field->get_size(); ++y) { + for (size_t x = 0; x < flow_field->get_size(); ++x) { + auto cell = flow_field->get_cell(x, y); + if (cell & FLOW_PATHABLE) { + Eigen::Affine3f arrow_model = Eigen::Affine3f::Identity(); + arrow_model.translate(Eigen::Vector3f(y + 0.5, 0, -1.0f * x - 0.5)); + auto dir = static_cast(cell & FLOW_DIR_MASK); + arrow_model.translate(offset_dir[dir]); + + auto rotation_rad = (cell & FLOW_DIR_MASK) * -45 * math::DEGSPERRAD; + arrow_model.rotate(Eigen::AngleAxisf(rotation_rad, Eigen::Vector3f::UnitY())); + auto arrow_unifs = ff_shader->new_uniform_input( + "model", + arrow_model.matrix(), + "view", + camera->get_view_matrix(), + "proj", + camera->get_projection_matrix(), + "color", + Eigen::Vector4f{0.0f, 0.0f, 0.0f, 1.0f}); + auto arrow_mesh = get_arrow_mesh(); + auto arrow_geometry = renderer->add_mesh_geometry(arrow_mesh); + renderer::Renderable arrow_renderable{ + arrow_unifs, + arrow_geometry, + true, + true, + }; + field_pass->add_renderables(arrow_renderable); + } + } + } + // Create object for the grid auto grid_unifs = grid_shader->new_uniform_input( "model", From 791fa602060c5f9d5f881a80c40ba84829a32b44 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sat, 24 Feb 2024 00:52:18 +0100 Subject: [PATCH 023/112] path: Change field that is rendered with F1-F4 keys. --- libopenage/pathfinding/demo/demo_0.cpp | 175 +++++++++++++++---------- 1 file changed, 103 insertions(+), 72 deletions(-) diff --git a/libopenage/pathfinding/demo/demo_0.cpp b/libopenage/pathfinding/demo/demo_0.cpp index 2e03a508b3..20ab91345f 100644 --- a/libopenage/pathfinding/demo/demo_0.cpp +++ b/libopenage/pathfinding/demo/demo_0.cpp @@ -2,6 +2,8 @@ #include "demo_0.h" +#include + #include "pathfinding/cost_field.h" #include "pathfinding/flow_field.h" #include "pathfinding/integration_field.h" @@ -595,6 +597,7 @@ void path_demo_0(const util::Path &path) { flow_field->build(integration_field); // Create object for the cost field + // this will be shown at the start Eigen::Matrix4f model = Eigen::Matrix4f::Identity(); auto cost_field_unifs = cf_shader->new_uniform_input( "model", @@ -611,89 +614,117 @@ void path_demo_0(const util::Path &path) { true, true, }; - // field_pass->add_renderables(cost_field_renderable); - - // Create object for the integration field - auto integration_field_unifs = if_shader->new_uniform_input( - "model", - model, - "view", - camera->get_view_matrix(), - "proj", - camera->get_projection_matrix()); - auto integration_field_mesh = get_integration_field_mesh(integration_field, 4); - auto integration_field_geometry = renderer->add_mesh_geometry(integration_field_mesh); - renderer::Renderable integration_field_renderable{ - integration_field_unifs, - integration_field_geometry, - true, - true, - }; - field_pass->add_renderables(integration_field_renderable); - - // Create object for the flow field - auto flow_field_unifs = ff_shader->new_uniform_input( - "model", - model, - "view", - camera->get_view_matrix(), - "proj", - camera->get_projection_matrix(), - "color", - Eigen::Vector4f{192.0 / 256, 255.0 / 256, 64.0 / 256, 1.0}); - auto flow_field_mesh = get_flow_field_mesh(flow_field); - auto flow_field_geometry = renderer->add_mesh_geometry(flow_field_mesh); - renderer::Renderable flow_field_renderable{ - flow_field_unifs, - flow_field_geometry, - true, - true, - }; - // field_pass->add_renderables(flow_field_renderable); - - std::unordered_map offset_dir{ - {flow_dir_t::NORTH, {-0.25f, 0.0f, 0.0f}}, - {flow_dir_t::NORTH_EAST, {-0.25f, 0.0f, -0.25f}}, - {flow_dir_t::EAST, {0.0f, 0.0f, -0.25f}}, - {flow_dir_t::SOUTH_EAST, {0.25f, 0.0f, -0.25f}}, - {flow_dir_t::SOUTH, {0.25f, 0.0f, 0.0f}}, - {flow_dir_t::SOUTH_WEST, {0.25f, 0.0f, 0.25f}}, - {flow_dir_t::WEST, {0.0f, 0.0f, 0.25f}}, - {flow_dir_t::NORTH_WEST, {-0.25f, 0.0f, 0.25f}}, - }; - - for (size_t y = 0; y < flow_field->get_size(); ++y) { - for (size_t x = 0; x < flow_field->get_size(); ++x) { - auto cell = flow_field->get_cell(x, y); - if (cell & FLOW_PATHABLE) { - Eigen::Affine3f arrow_model = Eigen::Affine3f::Identity(); - arrow_model.translate(Eigen::Vector3f(y + 0.5, 0, -1.0f * x - 0.5)); - auto dir = static_cast(cell & FLOW_DIR_MASK); - arrow_model.translate(offset_dir[dir]); - - auto rotation_rad = (cell & FLOW_DIR_MASK) * -45 * math::DEGSPERRAD; - arrow_model.rotate(Eigen::AngleAxisf(rotation_rad, Eigen::Vector3f::UnitY())); - auto arrow_unifs = ff_shader->new_uniform_input( + field_pass->add_renderables(cost_field_renderable); + + window.add_key_callback([&](const QKeyEvent &ev) { + if (ev.type() == QEvent::KeyRelease) { + if (ev.key() == Qt::Key_F1) { // Show cost field + // Recreate object for the cost field + Eigen::Matrix4f model = Eigen::Matrix4f::Identity(); + auto cost_field_unifs = cf_shader->new_uniform_input( + "model", + model, + "view", + camera->get_view_matrix(), + "proj", + camera->get_projection_matrix()); + auto cost_field_mesh = get_cost_field_mesh(cost_field); + auto cost_field_geometry = renderer->add_mesh_geometry(cost_field_mesh); + renderer::Renderable cost_field_renderable{ + cost_field_unifs, + cost_field_geometry, + true, + true, + }; + field_pass->set_renderables({cost_field_renderable}); + } + else if (ev.key() == Qt::Key_F2) { // Show integration field + // Create object for the integration field + auto integration_field_unifs = if_shader->new_uniform_input( + "model", + model, + "view", + camera->get_view_matrix(), + "proj", + camera->get_projection_matrix()); + auto integration_field_mesh = get_integration_field_mesh(integration_field, 4); + auto integration_field_geometry = renderer->add_mesh_geometry(integration_field_mesh); + renderer::Renderable integration_field_renderable{ + integration_field_unifs, + integration_field_geometry, + true, + true, + }; + field_pass->set_renderables({integration_field_renderable}); + } + else if (ev.key() == Qt::Key_F3) { // Show flow field + // Create object for the flow field + auto flow_field_unifs = ff_shader->new_uniform_input( "model", - arrow_model.matrix(), + model, "view", camera->get_view_matrix(), "proj", camera->get_projection_matrix(), "color", - Eigen::Vector4f{0.0f, 0.0f, 0.0f, 1.0f}); - auto arrow_mesh = get_arrow_mesh(); - auto arrow_geometry = renderer->add_mesh_geometry(arrow_mesh); - renderer::Renderable arrow_renderable{ - arrow_unifs, - arrow_geometry, + Eigen::Vector4f{192.0 / 256, 255.0 / 256, 64.0 / 256, 1.0}); + auto flow_field_mesh = get_flow_field_mesh(flow_field); + auto flow_field_geometry = renderer->add_mesh_geometry(flow_field_mesh); + renderer::Renderable flow_field_renderable{ + flow_field_unifs, + flow_field_geometry, true, true, }; - field_pass->add_renderables(arrow_renderable); + field_pass->set_renderables({flow_field_renderable}); + } + else if (ev.key() == Qt::Key_F4) { // Show steering vectors + static const std::unordered_map offset_dir{ + {flow_dir_t::NORTH, {-0.25f, 0.0f, 0.0f}}, + {flow_dir_t::NORTH_EAST, {-0.25f, 0.0f, -0.25f}}, + {flow_dir_t::EAST, {0.0f, 0.0f, -0.25f}}, + {flow_dir_t::SOUTH_EAST, {0.25f, 0.0f, -0.25f}}, + {flow_dir_t::SOUTH, {0.25f, 0.0f, 0.0f}}, + {flow_dir_t::SOUTH_WEST, {0.25f, 0.0f, 0.25f}}, + {flow_dir_t::WEST, {0.0f, 0.0f, 0.25f}}, + {flow_dir_t::NORTH_WEST, {-0.25f, 0.0f, 0.25f}}, + }; + + for (size_t y = 0; y < flow_field->get_size(); ++y) { + for (size_t x = 0; x < flow_field->get_size(); ++x) { + auto cell = flow_field->get_cell(x, y); + if (cell & FLOW_PATHABLE) { + Eigen::Affine3f arrow_model = Eigen::Affine3f::Identity(); + arrow_model.translate(Eigen::Vector3f(y + 0.5, 0, -1.0f * x - 0.5)); + auto dir = static_cast(cell & FLOW_DIR_MASK); + arrow_model.translate(offset_dir.at(dir)); + + auto rotation_rad = (cell & FLOW_DIR_MASK) * -45 * math::DEGSPERRAD; + arrow_model.rotate(Eigen::AngleAxisf(rotation_rad, Eigen::Vector3f::UnitY())); + auto arrow_unifs = ff_shader->new_uniform_input( + "model", + arrow_model.matrix(), + "view", + camera->get_view_matrix(), + "proj", + camera->get_projection_matrix(), + "color", + Eigen::Vector4f{0.0f, 0.0f, 0.0f, 1.0f}); + auto arrow_mesh = get_arrow_mesh(); + auto arrow_geometry = renderer->add_mesh_geometry(arrow_mesh); + renderer::Renderable arrow_renderable{ + arrow_unifs, + arrow_geometry, + true, + true, + }; + field_pass->add_renderables(arrow_renderable); + } + } + } } } - } + }); // Create object for the grid auto grid_unifs = grid_shader->new_uniform_input( From d24bc06e88a6e2bc7eb9878f3099e34498c1cc3c Mon Sep 17 00:00:00 2001 From: heinezen Date: Sat, 24 Feb 2024 01:36:00 +0100 Subject: [PATCH 024/112] path: Reset flow field values on build. --- libopenage/pathfinding/flow_field.cpp | 9 +++++++++ libopenage/pathfinding/flow_field.h | 5 +++++ 2 files changed, 14 insertions(+) diff --git a/libopenage/pathfinding/flow_field.cpp b/libopenage/pathfinding/flow_field.cpp index 27e6e14560..63d7bca860 100644 --- a/libopenage/pathfinding/flow_field.cpp +++ b/libopenage/pathfinding/flow_field.cpp @@ -39,6 +39,9 @@ void FlowField::build(const std::shared_ptr &integrate_field) << " does not match flow field size " << this->get_size() << "x" << this->get_size()); + // Reset the flow field. + this->reset(); + auto &integrate_cells = integrate_field->get_cells(); auto &flow_cells = this->cells; @@ -124,4 +127,10 @@ const std::vector &FlowField::get_cells() const { return this->cells; } +void FlowField::reset() { + for (auto &cell : this->cells) { + cell = 0; + } +} + } // namespace openage::path diff --git a/libopenage/pathfinding/flow_field.h b/libopenage/pathfinding/flow_field.h index 7fc3f00470..2fefd251ba 100644 --- a/libopenage/pathfinding/flow_field.h +++ b/libopenage/pathfinding/flow_field.h @@ -71,6 +71,11 @@ class FlowField { const std::vector &get_cells() const; private: + /** + * Reset the flow field values for rebuilding the field. + */ + void reset(); + /** * Side length of the field. */ From 0b0774ed2bfab9938b94d852e5fadf1c28cd2649 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sat, 24 Feb 2024 01:36:49 +0100 Subject: [PATCH 025/112] path: Change goal with right mouse button. --- libopenage/pathfinding/demo/demo_0.cpp | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/libopenage/pathfinding/demo/demo_0.cpp b/libopenage/pathfinding/demo/demo_0.cpp index 20ab91345f..0b101a7266 100644 --- a/libopenage/pathfinding/demo/demo_0.cpp +++ b/libopenage/pathfinding/demo/demo_0.cpp @@ -3,6 +3,7 @@ #include "demo_0.h" #include +#include #include "pathfinding/cost_field.h" #include "pathfinding/flow_field.h" @@ -596,6 +597,27 @@ void path_demo_0(const util::Path &path) { auto flow_field = std::make_shared(10); flow_field->build(integration_field); + window.add_mouse_button_callback([&](const QMouseEvent &ev) { + if (ev.type() == QEvent::MouseButtonRelease) { + if (ev.button() == Qt::RightButton) { + auto grid_plane_normal = Eigen::Vector3f{0, 1, 0}; + auto grid_plane_point = Eigen::Vector3f{0, 0, 0}; + auto camera_direction = renderer::camera::cam_direction; + auto camera_position = camera->get_input_pos( + coord::input{ev.position().x(), ev.position().y()}); + + Eigen::Vector3f intersect = camera_position + camera_direction * (grid_plane_point - camera_position).dot(grid_plane_normal) / camera_direction.dot(grid_plane_normal); + auto grid_x = static_cast(-1 * intersect[2]); + auto grid_y = static_cast(intersect[0]); + + if (grid_x >= 0 && grid_x < 10 && grid_y >= 0 && grid_y < 10) { + integration_field->integrate(cost_field, grid_x, grid_y); + flow_field->build(integration_field); + } + } + } + }); + // Create object for the cost field // this will be shown at the start Eigen::Matrix4f model = Eigen::Matrix4f::Identity(); From 89b210d5d7831c549cfd4865d18149d9de9fcfee Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 25 Feb 2024 00:44:47 +0100 Subject: [PATCH 026/112] path: Create RenderManager in demo for all graphics code. --- libopenage/pathfinding/demo/demo_0.cpp | 600 +++++++++++++++++++++++++ libopenage/pathfinding/demo/demo_0.h | 120 ++++- 2 files changed, 718 insertions(+), 2 deletions(-) diff --git a/libopenage/pathfinding/demo/demo_0.cpp b/libopenage/pathfinding/demo/demo_0.cpp index 0b101a7266..2649cb8dd1 100644 --- a/libopenage/pathfinding/demo/demo_0.cpp +++ b/libopenage/pathfinding/demo/demo_0.cpp @@ -781,4 +781,604 @@ void path_demo_0(const util::Path &path) { window.close(); } + +RenderManager::RenderManager(const std::shared_ptr &app, + const std::shared_ptr &window, + const util::Path &path) : + path{path}, + app{app}, + window{window}, + renderer{window->make_renderer()}, + camera{std::make_shared(renderer, window->get_size())} { + // Position camera to look at center of the grid + camera->look_at_coord({5, 5, 0}); + window->add_resize_callback([&](size_t w, size_t h, double /*scale*/) { + camera->resize(w, h); + }); + + this->init_shaders(); + this->init_passes(); +} + + +void RenderManager::run() { + while (not this->window->should_close()) { + this->app->process_events(); + + this->renderer->render(this->background_pass); + this->renderer->render(this->field_pass); + this->renderer->render(this->grid_pass); + this->renderer->render(this->display_pass); + + this->window->update(); + + this->renderer->check_error(); + } + this->window->close(); +} + +void RenderManager::show_cost_field(const std::shared_ptr &field) { + Eigen::Matrix4f model = Eigen::Matrix4f::Identity(); + auto unifs = this->cost_shader->new_uniform_input( + "model", + model, + "view", + this->camera->get_view_matrix(), + "proj", + this->camera->get_projection_matrix()); + auto mesh = RenderManager::get_cost_field_mesh(field); + auto geometry = this->renderer->add_mesh_geometry(mesh); + renderer::Renderable renderable{ + unifs, + geometry, + true, + true, + }; + this->field_pass->set_renderables({renderable}); +} + +void RenderManager::show_integration_field(const std::shared_ptr &field) { + Eigen::Matrix4f model = Eigen::Matrix4f::Identity(); + auto unifs = this->integration_shader->new_uniform_input( + "model", + model, + "view", + this->camera->get_view_matrix(), + "proj", + this->camera->get_projection_matrix()); + auto mesh = get_integration_field_mesh(field, 4); + auto geometry = this->renderer->add_mesh_geometry(mesh); + renderer::Renderable renderable{ + unifs, + geometry, + true, + true, + }; + this->field_pass->set_renderables({renderable}); +} + +void RenderManager::show_flow_field(const std::shared_ptr &field) { + Eigen::Matrix4f model = Eigen::Matrix4f::Identity(); + auto unifs = this->flow_shader->new_uniform_input( + "model", + model, + "view", + this->camera->get_view_matrix(), + "proj", + this->camera->get_projection_matrix(), + "color", + Eigen::Vector4f{192.0 / 256, 255.0 / 256, 64.0 / 256, 1.0}); + auto mesh = get_flow_field_mesh(field); + auto geometry = this->renderer->add_mesh_geometry(mesh); + renderer::Renderable renderable{ + unifs, + geometry, + true, + true, + }; + this->field_pass->set_renderables({renderable}); +} + +void RenderManager::init_shaders() { + // Shader sources + auto shaderdir = this->path / "assets" / "test" / "shaders" / "pathfinding"; + + // Shader for rendering the cost field + auto cf_vshader_file = shaderdir / "demo_0_cost_field.vert.glsl"; + auto cf_vshader_src = renderer::resources::ShaderSource( + renderer::resources::shader_lang_t::glsl, + renderer::resources::shader_stage_t::vertex, + cf_vshader_file); + + auto cf_fshader_file = shaderdir / "demo_0_cost_field.frag.glsl"; + auto cf_fshader_src = renderer::resources::ShaderSource( + renderer::resources::shader_lang_t::glsl, + renderer::resources::shader_stage_t::fragment, + cf_fshader_file); + + // Shader for rendering the integration field + auto if_vshader_file = shaderdir / "demo_0_integration_field.vert.glsl"; + auto if_vshader_src = renderer::resources::ShaderSource( + renderer::resources::shader_lang_t::glsl, + renderer::resources::shader_stage_t::vertex, + if_vshader_file); + + auto if_fshader_file = shaderdir / "demo_0_integration_field.frag.glsl"; + auto if_fshader_src = renderer::resources::ShaderSource( + renderer::resources::shader_lang_t::glsl, + renderer::resources::shader_stage_t::fragment, + if_fshader_file); + + // Shader for rendering the flow field + auto ff_vshader_file = shaderdir / "demo_0_flow_field.vert.glsl"; + auto ff_vshader_src = renderer::resources::ShaderSource( + renderer::resources::shader_lang_t::glsl, + renderer::resources::shader_stage_t::vertex, + ff_vshader_file); + + auto ff_fshader_file = shaderdir / "demo_0_flow_field.frag.glsl"; + auto ff_fshader_src = renderer::resources::ShaderSource( + renderer::resources::shader_lang_t::glsl, + renderer::resources::shader_stage_t::fragment, + ff_fshader_file); + + // Shader for rendering the grid + auto grid_vshader_file = shaderdir / "demo_0_grid.vert.glsl"; + auto grid_vshader_src = renderer::resources::ShaderSource( + renderer::resources::shader_lang_t::glsl, + renderer::resources::shader_stage_t::vertex, + grid_vshader_file); + + auto grid_fshader_file = shaderdir / "demo_0_grid.frag.glsl"; + auto grid_fshader_src = renderer::resources::ShaderSource( + renderer::resources::shader_lang_t::glsl, + renderer::resources::shader_stage_t::fragment, + grid_fshader_file); + + // Shader for monocolored objects + auto obj_vshader_file = shaderdir / "demo_0_obj.vert.glsl"; + auto obj_vshader_src = renderer::resources::ShaderSource( + renderer::resources::shader_lang_t::glsl, + renderer::resources::shader_stage_t::vertex, + obj_vshader_file); + + auto obj_fshader_file = shaderdir / "demo_0_obj.frag.glsl"; + auto obj_fshader_src = renderer::resources::ShaderSource( + renderer::resources::shader_lang_t::glsl, + renderer::resources::shader_stage_t::fragment, + obj_fshader_file); + + // Shader for rendering to the display target + auto display_vshader_file = shaderdir / "demo_0_display.vert.glsl"; + auto display_vshader_src = renderer::resources::ShaderSource( + renderer::resources::shader_lang_t::glsl, + renderer::resources::shader_stage_t::vertex, + display_vshader_file); + + auto display_fshader_file = shaderdir / "demo_0_display.frag.glsl"; + auto display_fshader_src = renderer::resources::ShaderSource( + renderer::resources::shader_lang_t::glsl, + renderer::resources::shader_stage_t::fragment, + display_fshader_file); + + // Create the shaders + this->cost_shader = renderer->add_shader({cf_vshader_src, cf_fshader_src}); + this->integration_shader = renderer->add_shader({if_vshader_src, if_fshader_src}); + this->flow_shader = renderer->add_shader({ff_vshader_src, ff_fshader_src}); + this->grid_shader = renderer->add_shader({grid_vshader_src, grid_fshader_src}); + this->obj_shader = renderer->add_shader({obj_vshader_src, obj_fshader_src}); + this->display_shader = renderer->add_shader({display_vshader_src, display_fshader_src}); +} + +void RenderManager::init_passes() { + auto size = this->window->get_size(); + + // Make a framebuffer for the background render pass to draw into + auto background_texture = renderer->add_texture( + renderer::resources::Texture2dInfo(size[0], + size[1], + renderer::resources::pixel_format::rgba8)); + auto depth_texture = renderer->add_texture( + renderer::resources::Texture2dInfo(size[0], + size[1], + renderer::resources::pixel_format::depth24)); + auto fbo = renderer->create_texture_target({background_texture, depth_texture}); + this->background_pass = renderer->add_render_pass({}, fbo); + + // Make a framebuffer for the field render passes to draw into + auto field_texture = renderer->add_texture( + renderer::resources::Texture2dInfo(size[0], + size[1], + renderer::resources::pixel_format::rgba8)); + auto depth_texture_2 = renderer->add_texture( + renderer::resources::Texture2dInfo(size[0], + size[1], + renderer::resources::pixel_format::depth24)); + auto field_fbo = renderer->create_texture_target({field_texture, depth_texture_2}); + this->field_pass = renderer->add_render_pass({}, field_fbo); + + // Make a framebuffer for the grid render passes to draw into + auto grid_texture = renderer->add_texture( + renderer::resources::Texture2dInfo(size[0], + size[1], + renderer::resources::pixel_format::rgba8)); + auto depth_texture_3 = renderer->add_texture( + renderer::resources::Texture2dInfo(size[0], + size[1], + renderer::resources::pixel_format::depth24)); + auto grid_fbo = renderer->create_texture_target({grid_texture, depth_texture_3}); + this->grid_pass = renderer->add_render_pass({}, grid_fbo); + + // Make two objects that draw the results of the previous passes onto the screen + // in the display render pass + auto bg_texture_unif = display_shader->new_uniform_input("color_texture", background_texture); + auto quad = renderer->add_mesh_geometry(renderer::resources::MeshData::make_quad()); + renderer::Renderable bg_pass_obj{ + bg_texture_unif, + quad, + true, + true, + }; + auto field_texture_unif = display_shader->new_uniform_input("color_texture", field_texture); + renderer::Renderable field_pass_obj{ + field_texture_unif, + quad, + true, + true, + }; + auto grid_texture_unif = display_shader->new_uniform_input("color_texture", grid_texture); + renderer::Renderable grid_pass_obj{ + grid_texture_unif, + quad, + true, + true, + }; + this->display_pass = renderer->add_render_pass( + {bg_pass_obj, field_pass_obj, grid_pass_obj}, + renderer->get_display_target()); +} + +renderer::resources::MeshData RenderManager::get_cost_field_mesh(const std::shared_ptr &field, + size_t resolution) { + // increase by 1 in every dimension because to get the vertex length + // of each dimension + util::Vector2s size{ + field->get_size() * resolution + 1, + field->get_size() * resolution + 1, + }; + auto vert_distance = 1.0f / resolution; + + // add vertices for the cells of the grid + std::vector verts{}; + auto vert_count = size[0] * size[1]; + verts.reserve(vert_count * 4); + for (int i = 0; i < static_cast(size[0]); ++i) { + for (int j = 0; j < static_cast(size[1]); ++j) { + // for each vertex, compare the surrounding tiles + std::vector surround{}; + if (j - 1 >= 0 and i - 1 >= 0) { + auto cost = field->get_cost((i - 1) / resolution, (j - 1) / resolution); + surround.push_back(cost); + } + if (j < static_cast(field->get_size()) and i - 1 >= 0) { + auto cost = field->get_cost((i - 1) / resolution, j / resolution); + surround.push_back(cost); + } + if (j < static_cast(field->get_size()) and i < static_cast(field->get_size())) { + auto cost = field->get_cost(i / resolution, j / resolution); + surround.push_back(cost); + } + if (j - 1 >= 0 and i < static_cast(field->get_size())) { + auto cost = field->get_cost(i / resolution, (j - 1) / resolution); + surround.push_back(cost); + } + // use the cost of the most expensive surrounding tile + auto max_cost = *std::max_element(surround.begin(), surround.end()); + + coord::scene3 v{ + static_cast(i * vert_distance), + static_cast(j * vert_distance), + 0, + }; + auto world_v = v.to_world_space(); + verts.push_back(world_v[0]); + verts.push_back(world_v[1]); + verts.push_back(world_v[2]); + verts.push_back(max_cost); + } + } + + // split the grid into triangles using an index array + std::vector idxs; + idxs.reserve((size[0] - 1) * (size[1] - 1) * 6); + // iterate over all tiles in the grid by columns, i.e. starting + // from the left corner to the bottom corner if you imagine it from + // the camera's point of view + for (size_t i = 0; i < size[0] - 1; ++i) { + for (size_t j = 0; j < size[1] - 1; ++j) { + // since we are working on tiles, we split each tile into two triangles + // with counter-clockwise vertex order + idxs.push_back(j + i * size[1]); // bottom left + idxs.push_back(j + 1 + i * size[1]); // bottom right + idxs.push_back(j + size[1] + i * size[1]); // top left + idxs.push_back(j + 1 + i * size[1]); // bottom right + idxs.push_back(j + size[1] + 1 + i * size[1]); // top right + idxs.push_back(j + size[1] + i * size[1]); // top left + } + } + + renderer::resources::VertexInputInfo info{ + {renderer::resources::vertex_input_t::V3F32, renderer::resources::vertex_input_t::F32}, + renderer::resources::vertex_layout_t::AOS, + renderer::resources::vertex_primitive_t::TRIANGLES, + renderer::resources::index_t::U16}; + + auto const vert_data_size = verts.size() * sizeof(float); + std::vector vert_data(vert_data_size); + std::memcpy(vert_data.data(), verts.data(), vert_data_size); + + auto const idx_data_size = idxs.size() * sizeof(uint16_t); + std::vector idx_data(idx_data_size); + std::memcpy(idx_data.data(), idxs.data(), idx_data_size); + + return {std::move(vert_data), std::move(idx_data), info}; +} + +renderer::resources::MeshData RenderManager::get_integration_field_mesh(const std::shared_ptr &field, + size_t resolution) { + // increase by 1 in every dimension because to get the vertex length + // of each dimension + util::Vector2s size{ + field->get_size() * resolution + 1, + field->get_size() * resolution + 1, + }; + auto vert_distance = 1.0f / resolution; + + // add vertices for the cells of the grid + std::vector verts{}; + auto vert_count = size[0] * size[1]; + verts.reserve(vert_count * 4); + for (int i = 0; i < static_cast(size[0]); ++i) { + for (int j = 0; j < static_cast(size[1]); ++j) { + // for each vertex, compare the surrounding tiles + std::vector surround{}; + if (j - 1 >= 0 and i - 1 >= 0) { + auto cost = field->get_cell((i - 1) / resolution, (j - 1) / resolution); + surround.push_back(cost); + } + if (j < static_cast(field->get_size()) and i - 1 >= 0) { + auto cost = field->get_cell((i - 1) / resolution, j / resolution); + surround.push_back(cost); + } + if (j < static_cast(field->get_size()) and i < static_cast(field->get_size())) { + auto cost = field->get_cell(i / resolution, j / resolution); + surround.push_back(cost); + } + if (j - 1 >= 0 and i < static_cast(field->get_size())) { + auto cost = field->get_cell(i / resolution, (j - 1) / resolution); + surround.push_back(cost); + } + // use the cost of the most expensive surrounding tile + auto max_cost = *std::max_element(surround.begin(), surround.end()); + + coord::scene3 v{ + static_cast(i * vert_distance), + static_cast(j * vert_distance), + 0, + }; + auto world_v = v.to_world_space(); + verts.push_back(world_v[0]); + verts.push_back(world_v[1]); + verts.push_back(world_v[2]); + verts.push_back(max_cost); + } + } + + // split the grid into triangles using an index array + std::vector idxs; + idxs.reserve((size[0] - 1) * (size[1] - 1) * 6); + // iterate over all tiles in the grid by columns, i.e. starting + // from the left corner to the bottom corner if you imagine it from + // the camera's point of view + for (size_t i = 0; i < size[0] - 1; ++i) { + for (size_t j = 0; j < size[1] - 1; ++j) { + // since we are working on tiles, we split each tile into two triangles + // with counter-clockwise vertex order + idxs.push_back(j + i * size[1]); // bottom left + idxs.push_back(j + 1 + i * size[1]); // bottom right + idxs.push_back(j + size[1] + i * size[1]); // top left + idxs.push_back(j + 1 + i * size[1]); // bottom right + idxs.push_back(j + size[1] + 1 + i * size[1]); // top right + idxs.push_back(j + size[1] + i * size[1]); // top left + } + } + + renderer::resources::VertexInputInfo info{ + {renderer::resources::vertex_input_t::V3F32, renderer::resources::vertex_input_t::F32}, + renderer::resources::vertex_layout_t::AOS, + renderer::resources::vertex_primitive_t::TRIANGLES, + renderer::resources::index_t::U16}; + + auto const vert_data_size = verts.size() * sizeof(float); + std::vector vert_data(vert_data_size); + std::memcpy(vert_data.data(), verts.data(), vert_data_size); + + auto const idx_data_size = idxs.size() * sizeof(uint16_t); + std::vector idx_data(idx_data_size); + std::memcpy(idx_data.data(), idxs.data(), idx_data_size); + + return {std::move(vert_data), std::move(idx_data), info}; +} + +renderer::resources::MeshData RenderManager::get_flow_field_mesh(const std::shared_ptr &field, + size_t resolution) { + // increase by 1 in every dimension because to get the vertex length + // of each dimension + util::Vector2s size{ + field->get_size() * resolution + 1, + field->get_size() * resolution + 1, + }; + auto vert_distance = 1.0f / resolution; + + // add vertices for the cells of the grid + std::vector verts{}; + auto vert_count = size[0] * size[1]; + verts.reserve(vert_count * 4); + for (int i = 0; i < static_cast(size[0]); ++i) { + for (int j = 0; j < static_cast(size[1]); ++j) { + // for each vertex, compare the surrounding tiles + std::vector surround{}; + if (j - 1 >= 0 and i - 1 >= 0) { + auto cost = field->get_cell((i - 1) / resolution, (j - 1) / resolution); + surround.push_back(cost); + } + if (j < static_cast(field->get_size()) and i - 1 >= 0) { + auto cost = field->get_cell((i - 1) / resolution, j / resolution); + surround.push_back(cost); + } + if (j < static_cast(field->get_size()) and i < static_cast(field->get_size())) { + auto cost = field->get_cell(i / resolution, j / resolution); + surround.push_back(cost); + } + if (j - 1 >= 0 and i < static_cast(field->get_size())) { + auto cost = field->get_cell(i / resolution, (j - 1) / resolution); + surround.push_back(cost); + } + // use the cost of the most expensive surrounding tile + auto max_cost = *std::max_element(surround.begin(), surround.end()); + + coord::scene3 v{ + static_cast(i * vert_distance), + static_cast(j * vert_distance), + 0, + }; + auto world_v = v.to_world_space(); + verts.push_back(world_v[0]); + verts.push_back(world_v[1]); + verts.push_back(world_v[2]); + verts.push_back(max_cost); + } + } + + // split the grid into triangles using an index array + std::vector idxs; + idxs.reserve((size[0] - 1) * (size[1] - 1) * 6); + // iterate over all tiles in the grid by columns, i.e. starting + // from the left corner to the bottom corner if you imagine it from + // the camera's point of view + for (size_t i = 0; i < size[0] - 1; ++i) { + for (size_t j = 0; j < size[1] - 1; ++j) { + // since we are working on tiles, we split each tile into two triangles + // with counter-clockwise vertex order + idxs.push_back(j + i * size[1]); // bottom left + idxs.push_back(j + 1 + i * size[1]); // bottom right + idxs.push_back(j + size[1] + i * size[1]); // top left + idxs.push_back(j + 1 + i * size[1]); // bottom right + idxs.push_back(j + size[1] + 1 + i * size[1]); // top right + idxs.push_back(j + size[1] + i * size[1]); // top left + } + } + + renderer::resources::VertexInputInfo info{ + {renderer::resources::vertex_input_t::V3F32, renderer::resources::vertex_input_t::F32}, + renderer::resources::vertex_layout_t::AOS, + renderer::resources::vertex_primitive_t::TRIANGLES, + renderer::resources::index_t::U16}; + + auto const vert_data_size = verts.size() * sizeof(float); + std::vector vert_data(vert_data_size); + std::memcpy(vert_data.data(), verts.data(), vert_data_size); + + auto const idx_data_size = idxs.size() * sizeof(uint16_t); + std::vector idx_data(idx_data_size); + std::memcpy(idx_data.data(), idxs.data(), idx_data_size); + + return {std::move(vert_data), std::move(idx_data), info}; +} + +renderer::resources::MeshData RenderManager::get_arrow_mesh() { + // vertices for the arrow + // x, y, z + std::vector verts{ + // clang-format off + 0.2f, 0.01f, -0.05f, + 0.2f, 0.01f, 0.05f, + -0.2f, 0.01f, -0.05f, + -0.2f, 0.01f, 0.05f, + // clang-format on + }; + + renderer::resources::VertexInputInfo info{ + {renderer::resources::vertex_input_t::V3F32}, + renderer::resources::vertex_layout_t::AOS, + renderer::resources::vertex_primitive_t::TRIANGLE_STRIP}; + + auto const vert_data_size = verts.size() * sizeof(float); + std::vector vert_data(vert_data_size); + std::memcpy(vert_data.data(), verts.data(), vert_data_size); + + return {std::move(vert_data), info}; +} + +renderer::resources::MeshData RenderManager::get_grid_mesh(size_t side_length) { + // increase by 1 in every dimension because to get the vertex length + // of each dimension + util::Vector2s size{side_length + 1, side_length + 1}; + + // add vertices for the cells of the grid + std::vector verts{}; + auto vert_count = size[0] * size[1]; + verts.reserve(vert_count * 3); + for (int i = 0; i < (int)size[0]; ++i) { + for (int j = 0; j < (int)size[1]; ++j) { + coord::scene3 v{ + static_cast(i), + static_cast(j), + 0, + }; + auto world_v = v.to_world_space(); + verts.push_back(world_v[0]); + verts.push_back(world_v[1]); + verts.push_back(world_v[2]); + } + } + + // split the grid into lines using an index array + std::vector idxs; + idxs.reserve((size[0] - 1) * (size[1] - 1) * 8); + // iterate over all tiles in the grid by columns, i.e. starting + // from the left corner to the bottom corner if you imagine it from + // the camera's point of view + for (size_t i = 0; i < size[0] - 1; ++i) { + for (size_t j = 0; j < size[1] - 1; ++j) { + // since we are working on tiles, we just draw a square using the 4 vertices + idxs.push_back(j + i * size[1]); // bottom left + idxs.push_back(j + 1 + i * size[1]); // bottom right + idxs.push_back(j + 1 + i * size[1]); // bottom right + idxs.push_back(j + size[1] + 1 + i * size[1]); // top right + idxs.push_back(j + size[1] + 1 + i * size[1]); // top right + idxs.push_back(j + size[1] + i * size[1]); // top left + idxs.push_back(j + size[1] + i * size[1]); // top left + idxs.push_back(j + i * size[1]); // bottom left + } + } + + renderer::resources::VertexInputInfo info{ + {renderer::resources::vertex_input_t::V3F32}, + renderer::resources::vertex_layout_t::AOS, + renderer::resources::vertex_primitive_t::LINES, + renderer::resources::index_t::U16}; + + auto const vert_data_size = verts.size() * sizeof(float); + std::vector vert_data(vert_data_size); + std::memcpy(vert_data.data(), verts.data(), vert_data_size); + + auto const idx_data_size = idxs.size() * sizeof(uint16_t); + std::vector idx_data(idx_data_size); + std::memcpy(idx_data.data(), idxs.data(), idx_data_size); + + return {std::move(vert_data), std::move(idx_data), info}; +} + + } // namespace openage::path::tests diff --git a/libopenage/pathfinding/demo/demo_0.h b/libopenage/pathfinding/demo/demo_0.h index 5758b9c33e..4ab4a8d327 100644 --- a/libopenage/pathfinding/demo/demo_0.h +++ b/libopenage/pathfinding/demo/demo_0.h @@ -2,10 +2,37 @@ #pragma once +#include + #include "util/path.h" -namespace openage::path::tests { +namespace openage { +namespace renderer { +class RenderPass; +class Renderer; +class ShaderProgram; +class Window; + +namespace camera { +class Camera; +} // namespace camera + +namespace gui { +class GuiApplicationWithLogger; +} // namespace gui + +namespace resources { +class MeshData; +} // namespace resources +} // namespace renderer + +namespace path { +class CostField; +class IntegrationField; +class FlowField; + +namespace tests { /** * Show the pathfinding functionality of the path module: @@ -20,4 +47,93 @@ namespace openage::path::tests { void path_demo_0(const util::Path &path); -} // namespace openage::path::tests +class RenderManager { +public: + RenderManager(const std::shared_ptr &app, + const std::shared_ptr &window, + const util::Path &path); + ~RenderManager() = default; + + void run(); + + void show_cost_field(const std::shared_ptr &field); + void show_integration_field(const std::shared_ptr &field); + void show_flow_field(const std::shared_ptr &field); + +private: + void init_shaders(); + void init_passes(); + + + /** + * Create a mesh for the cost field. + * + * @param field Cost field to visualize. + * @param resolution Determines the number of subdivisions per grid cell. The number of + * quads per cell is resolution^2. (default = 2) + * + * @return Mesh data for the cost field. + */ + static renderer::resources::MeshData get_cost_field_mesh(const std::shared_ptr &field, + size_t resolution = 2); + + /** + * Create a mesh for the integration field. + * + * @param field Integration field to visualize. + * @param resolution Determines the number of subdivisions per grid cell. The number of + * quads per cell is resolution^2. (default = 2) + * + * @return Mesh data for the integration field. + */ + static renderer::resources::MeshData get_integration_field_mesh(const std::shared_ptr &field, + size_t resolution = 2); + + /** + * Create a mesh for the flow field. + * + * @param field Flow field to visualize. + */ + static renderer::resources::MeshData get_flow_field_mesh(const std::shared_ptr &field, + size_t resolution = 2); + + /** + * Create a mesh for an arrow. + * + * @return Mesh data for an arrow. + */ + static renderer::resources::MeshData get_arrow_mesh(); + + /** + * Create a mesh for the grid. + * + * @param side_length Length of the grid's side. + * + * @return Mesh data for the grid. + */ + static renderer::resources::MeshData get_grid_mesh(size_t side_length); + + + const util::Path &path; + + std::shared_ptr app; + std::shared_ptr window; + std::shared_ptr renderer; + std::shared_ptr camera; + + std::shared_ptr cost_shader; + std::shared_ptr integration_shader; + std::shared_ptr flow_shader; + std::shared_ptr grid_shader; + std::shared_ptr obj_shader; + std::shared_ptr display_shader; + + std::shared_ptr background_pass; + std::shared_ptr field_pass; + std::shared_ptr grid_pass; + std::shared_ptr display_pass; +}; + +} // namespace tests +} // namespace path +} // namespace openage From 53bee9dfe4d3d953a7ccef38603158654b04c1e8 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 25 Feb 2024 01:22:47 +0100 Subject: [PATCH 027/112] path: Remove old renderer code from demo. --- libopenage/pathfinding/demo/demo_0.cpp | 852 ++++--------------------- libopenage/pathfinding/demo/demo_0.h | 119 +++- 2 files changed, 245 insertions(+), 726 deletions(-) diff --git a/libopenage/pathfinding/demo/demo_0.cpp b/libopenage/pathfinding/demo/demo_0.cpp index 2649cb8dd1..796c1cc97e 100644 --- a/libopenage/pathfinding/demo/demo_0.cpp +++ b/libopenage/pathfinding/demo/demo_0.cpp @@ -22,567 +22,13 @@ namespace openage::path::tests { -/** - * Create a mesh for the cost field. - * - * @param field Cost field to visualize. - * @param resolution Determines the number of subdivisions per grid cell. The number of - * quads per cell is resolution^2. (default = 2) - * - * @return Mesh data for the cost field. - */ -renderer::resources::MeshData get_cost_field_mesh(const std::shared_ptr &field, - size_t resolution = 2) { - // increase by 1 in every dimension because to get the vertex length - // of each dimension - util::Vector2s size{ - field->get_size() * resolution + 1, - field->get_size() * resolution + 1, - }; - auto vert_distance = 1.0f / resolution; - - // add vertices for the cells of the grid - std::vector verts{}; - auto vert_count = size[0] * size[1]; - verts.reserve(vert_count * 4); - for (int i = 0; i < static_cast(size[0]); ++i) { - for (int j = 0; j < static_cast(size[1]); ++j) { - // for each vertex, compare the surrounding tiles - std::vector surround{}; - if (j - 1 >= 0 and i - 1 >= 0) { - auto cost = field->get_cost((i - 1) / resolution, (j - 1) / resolution); - surround.push_back(cost); - } - if (j < static_cast(field->get_size()) and i - 1 >= 0) { - auto cost = field->get_cost((i - 1) / resolution, j / resolution); - surround.push_back(cost); - } - if (j < static_cast(field->get_size()) and i < static_cast(field->get_size())) { - auto cost = field->get_cost(i / resolution, j / resolution); - surround.push_back(cost); - } - if (j - 1 >= 0 and i < static_cast(field->get_size())) { - auto cost = field->get_cost(i / resolution, (j - 1) / resolution); - surround.push_back(cost); - } - // use the cost of the most expensive surrounding tile - auto max_cost = *std::max_element(surround.begin(), surround.end()); - - coord::scene3 v{ - static_cast(i * vert_distance), - static_cast(j * vert_distance), - 0, - }; - auto world_v = v.to_world_space(); - verts.push_back(world_v[0]); - verts.push_back(world_v[1]); - verts.push_back(world_v[2]); - verts.push_back(max_cost); - } - } - - // split the grid into triangles using an index array - std::vector idxs; - idxs.reserve((size[0] - 1) * (size[1] - 1) * 6); - // iterate over all tiles in the grid by columns, i.e. starting - // from the left corner to the bottom corner if you imagine it from - // the camera's point of view - for (size_t i = 0; i < size[0] - 1; ++i) { - for (size_t j = 0; j < size[1] - 1; ++j) { - // since we are working on tiles, we split each tile into two triangles - // with counter-clockwise vertex order - idxs.push_back(j + i * size[1]); // bottom left - idxs.push_back(j + 1 + i * size[1]); // bottom right - idxs.push_back(j + size[1] + i * size[1]); // top left - idxs.push_back(j + 1 + i * size[1]); // bottom right - idxs.push_back(j + size[1] + 1 + i * size[1]); // top right - idxs.push_back(j + size[1] + i * size[1]); // top left - } - } - - renderer::resources::VertexInputInfo info{ - {renderer::resources::vertex_input_t::V3F32, renderer::resources::vertex_input_t::F32}, - renderer::resources::vertex_layout_t::AOS, - renderer::resources::vertex_primitive_t::TRIANGLES, - renderer::resources::index_t::U16}; - - auto const vert_data_size = verts.size() * sizeof(float); - std::vector vert_data(vert_data_size); - std::memcpy(vert_data.data(), verts.data(), vert_data_size); - - auto const idx_data_size = idxs.size() * sizeof(uint16_t); - std::vector idx_data(idx_data_size); - std::memcpy(idx_data.data(), idxs.data(), idx_data_size); - - return {std::move(vert_data), std::move(idx_data), info}; -} - - -/** - * Create a mesh for the integration field. - * - * @param field Integration field to visualize. - * @param resolution Determines the number of subdivisions per grid cell. The number of - * quads per cell is resolution^2. (default = 2) - * - * @return Mesh data for the integration field. - */ -renderer::resources::MeshData get_integration_field_mesh(const std::shared_ptr &field, - size_t resolution = 2) { - // increase by 1 in every dimension because to get the vertex length - // of each dimension - util::Vector2s size{ - field->get_size() * resolution + 1, - field->get_size() * resolution + 1, - }; - auto vert_distance = 1.0f / resolution; - - // add vertices for the cells of the grid - std::vector verts{}; - auto vert_count = size[0] * size[1]; - verts.reserve(vert_count * 4); - for (int i = 0; i < static_cast(size[0]); ++i) { - for (int j = 0; j < static_cast(size[1]); ++j) { - // for each vertex, compare the surrounding tiles - std::vector surround{}; - if (j - 1 >= 0 and i - 1 >= 0) { - auto cost = field->get_cell((i - 1) / resolution, (j - 1) / resolution); - surround.push_back(cost); - } - if (j < static_cast(field->get_size()) and i - 1 >= 0) { - auto cost = field->get_cell((i - 1) / resolution, j / resolution); - surround.push_back(cost); - } - if (j < static_cast(field->get_size()) and i < static_cast(field->get_size())) { - auto cost = field->get_cell(i / resolution, j / resolution); - surround.push_back(cost); - } - if (j - 1 >= 0 and i < static_cast(field->get_size())) { - auto cost = field->get_cell(i / resolution, (j - 1) / resolution); - surround.push_back(cost); - } - // use the cost of the most expensive surrounding tile - auto max_cost = *std::max_element(surround.begin(), surround.end()); - - coord::scene3 v{ - static_cast(i * vert_distance), - static_cast(j * vert_distance), - 0, - }; - auto world_v = v.to_world_space(); - verts.push_back(world_v[0]); - verts.push_back(world_v[1]); - verts.push_back(world_v[2]); - verts.push_back(max_cost); - } - } - - // split the grid into triangles using an index array - std::vector idxs; - idxs.reserve((size[0] - 1) * (size[1] - 1) * 6); - // iterate over all tiles in the grid by columns, i.e. starting - // from the left corner to the bottom corner if you imagine it from - // the camera's point of view - for (size_t i = 0; i < size[0] - 1; ++i) { - for (size_t j = 0; j < size[1] - 1; ++j) { - // since we are working on tiles, we split each tile into two triangles - // with counter-clockwise vertex order - idxs.push_back(j + i * size[1]); // bottom left - idxs.push_back(j + 1 + i * size[1]); // bottom right - idxs.push_back(j + size[1] + i * size[1]); // top left - idxs.push_back(j + 1 + i * size[1]); // bottom right - idxs.push_back(j + size[1] + 1 + i * size[1]); // top right - idxs.push_back(j + size[1] + i * size[1]); // top left - } - } - - renderer::resources::VertexInputInfo info{ - {renderer::resources::vertex_input_t::V3F32, renderer::resources::vertex_input_t::F32}, - renderer::resources::vertex_layout_t::AOS, - renderer::resources::vertex_primitive_t::TRIANGLES, - renderer::resources::index_t::U16}; - - auto const vert_data_size = verts.size() * sizeof(float); - std::vector vert_data(vert_data_size); - std::memcpy(vert_data.data(), verts.data(), vert_data_size); - - auto const idx_data_size = idxs.size() * sizeof(uint16_t); - std::vector idx_data(idx_data_size); - std::memcpy(idx_data.data(), idxs.data(), idx_data_size); - - return {std::move(vert_data), std::move(idx_data), info}; -} - -/** - * Create a mesh for the flow field. - * - * @param field Flow field to visualize. - */ -renderer::resources::MeshData get_flow_field_mesh(const std::shared_ptr &field, - size_t resolution = 2) { - // increase by 1 in every dimension because to get the vertex length - // of each dimension - util::Vector2s size{ - field->get_size() * resolution + 1, - field->get_size() * resolution + 1, - }; - auto vert_distance = 1.0f / resolution; - - // add vertices for the cells of the grid - std::vector verts{}; - auto vert_count = size[0] * size[1]; - verts.reserve(vert_count * 4); - for (int i = 0; i < static_cast(size[0]); ++i) { - for (int j = 0; j < static_cast(size[1]); ++j) { - // for each vertex, compare the surrounding tiles - std::vector surround{}; - if (j - 1 >= 0 and i - 1 >= 0) { - auto cost = field->get_cell((i - 1) / resolution, (j - 1) / resolution); - surround.push_back(cost); - } - if (j < static_cast(field->get_size()) and i - 1 >= 0) { - auto cost = field->get_cell((i - 1) / resolution, j / resolution); - surround.push_back(cost); - } - if (j < static_cast(field->get_size()) and i < static_cast(field->get_size())) { - auto cost = field->get_cell(i / resolution, j / resolution); - surround.push_back(cost); - } - if (j - 1 >= 0 and i < static_cast(field->get_size())) { - auto cost = field->get_cell(i / resolution, (j - 1) / resolution); - surround.push_back(cost); - } - // use the cost of the most expensive surrounding tile - auto max_cost = *std::max_element(surround.begin(), surround.end()); - - coord::scene3 v{ - static_cast(i * vert_distance), - static_cast(j * vert_distance), - 0, - }; - auto world_v = v.to_world_space(); - verts.push_back(world_v[0]); - verts.push_back(world_v[1]); - verts.push_back(world_v[2]); - verts.push_back(max_cost); - } - } - - // split the grid into triangles using an index array - std::vector idxs; - idxs.reserve((size[0] - 1) * (size[1] - 1) * 6); - // iterate over all tiles in the grid by columns, i.e. starting - // from the left corner to the bottom corner if you imagine it from - // the camera's point of view - for (size_t i = 0; i < size[0] - 1; ++i) { - for (size_t j = 0; j < size[1] - 1; ++j) { - // since we are working on tiles, we split each tile into two triangles - // with counter-clockwise vertex order - idxs.push_back(j + i * size[1]); // bottom left - idxs.push_back(j + 1 + i * size[1]); // bottom right - idxs.push_back(j + size[1] + i * size[1]); // top left - idxs.push_back(j + 1 + i * size[1]); // bottom right - idxs.push_back(j + size[1] + 1 + i * size[1]); // top right - idxs.push_back(j + size[1] + i * size[1]); // top left - } - } - - renderer::resources::VertexInputInfo info{ - {renderer::resources::vertex_input_t::V3F32, renderer::resources::vertex_input_t::F32}, - renderer::resources::vertex_layout_t::AOS, - renderer::resources::vertex_primitive_t::TRIANGLES, - renderer::resources::index_t::U16}; - - auto const vert_data_size = verts.size() * sizeof(float); - std::vector vert_data(vert_data_size); - std::memcpy(vert_data.data(), verts.data(), vert_data_size); - - auto const idx_data_size = idxs.size() * sizeof(uint16_t); - std::vector idx_data(idx_data_size); - std::memcpy(idx_data.data(), idxs.data(), idx_data_size); - - return {std::move(vert_data), std::move(idx_data), info}; -} - -/** - * Create a mesh for an arrow. - * - * @return Mesh data for an arrow. - */ -renderer::resources::MeshData get_arrow_mesh() { - // vertices for the arrow - // x, y, z - std::vector verts{ - // clang-format off - 0.2f, 0.01f, -0.05f, - 0.2f, 0.01f, 0.05f, - -0.2f, 0.01f, -0.05f, - -0.2f, 0.01f, 0.05f, - // clang-format on - }; - - renderer::resources::VertexInputInfo info{ - {renderer::resources::vertex_input_t::V3F32}, - renderer::resources::vertex_layout_t::AOS, - renderer::resources::vertex_primitive_t::TRIANGLE_STRIP}; - - auto const vert_data_size = verts.size() * sizeof(float); - std::vector vert_data(vert_data_size); - std::memcpy(vert_data.data(), verts.data(), vert_data_size); - - return {std::move(vert_data), info}; -} - - -/** - * Create a mesh for the grid. - * - * @param side_length Length of the grid's side. - * - * @return Mesh data for the grid. - */ -renderer::resources::MeshData get_grid_mesh(size_t side_length) { - // increase by 1 in every dimension because to get the vertex length - // of each dimension - util::Vector2s size{side_length + 1, side_length + 1}; - - // add vertices for the cells of the grid - std::vector verts{}; - auto vert_count = size[0] * size[1]; - verts.reserve(vert_count * 3); - for (int i = 0; i < (int)size[0]; ++i) { - for (int j = 0; j < (int)size[1]; ++j) { - coord::scene3 v{ - static_cast(i), - static_cast(j), - 0, - }; - auto world_v = v.to_world_space(); - verts.push_back(world_v[0]); - verts.push_back(world_v[1]); - verts.push_back(world_v[2]); - } - } - - // split the grid into lines using an index array - std::vector idxs; - idxs.reserve((size[0] - 1) * (size[1] - 1) * 8); - // iterate over all tiles in the grid by columns, i.e. starting - // from the left corner to the bottom corner if you imagine it from - // the camera's point of view - for (size_t i = 0; i < size[0] - 1; ++i) { - for (size_t j = 0; j < size[1] - 1; ++j) { - // since we are working on tiles, we just draw a square using the 4 vertices - idxs.push_back(j + i * size[1]); // bottom left - idxs.push_back(j + 1 + i * size[1]); // bottom right - idxs.push_back(j + 1 + i * size[1]); // bottom right - idxs.push_back(j + size[1] + 1 + i * size[1]); // top right - idxs.push_back(j + size[1] + 1 + i * size[1]); // top right - idxs.push_back(j + size[1] + i * size[1]); // top left - idxs.push_back(j + size[1] + i * size[1]); // top left - idxs.push_back(j + i * size[1]); // bottom left - } - } - - renderer::resources::VertexInputInfo info{ - {renderer::resources::vertex_input_t::V3F32}, - renderer::resources::vertex_layout_t::AOS, - renderer::resources::vertex_primitive_t::LINES, - renderer::resources::index_t::U16}; - - auto const vert_data_size = verts.size() * sizeof(float); - std::vector vert_data(vert_data_size); - std::memcpy(vert_data.data(), verts.data(), vert_data_size); - - auto const idx_data_size = idxs.size() * sizeof(uint16_t); - std::vector idx_data(idx_data_size); - std::memcpy(idx_data.data(), idxs.data(), idx_data_size); - - return {std::move(vert_data), std::move(idx_data), info}; -} - -void path_demo_0(const util::Path &path) { - auto qtapp = std::make_shared(); - - renderer::opengl::GlWindow window("openage pathfinding test", 1440, 720, true); - auto renderer = window.make_renderer(); - - // Camera for correct projection of terrain - auto camera = std::make_shared(renderer, window.get_size()); - camera->look_at_coord({5, 5, 0}); - window.add_resize_callback([&](size_t w, size_t h, double /*scale*/) { - camera->resize(w, h); - }); - - // Shader sources - auto shaderdir = path / "assets" / "test" / "shaders" / "pathfinding"; - - // Shader for rendering the cost field - auto cf_vshader_file = shaderdir / "demo_0_cost_field.vert.glsl"; - auto cf_vshader_src = renderer::resources::ShaderSource( - renderer::resources::shader_lang_t::glsl, - renderer::resources::shader_stage_t::vertex, - cf_vshader_file); - - auto cf_fshader_file = shaderdir / "demo_0_cost_field.frag.glsl"; - auto cf_fshader_src = renderer::resources::ShaderSource( - renderer::resources::shader_lang_t::glsl, - renderer::resources::shader_stage_t::fragment, - cf_fshader_file); - - // Shader for rendering the integration field - auto if_vshader_file = shaderdir / "demo_0_integration_field.vert.glsl"; - auto if_vshader_src = renderer::resources::ShaderSource( - renderer::resources::shader_lang_t::glsl, - renderer::resources::shader_stage_t::vertex, - if_vshader_file); - - auto if_fshader_file = shaderdir / "demo_0_integration_field.frag.glsl"; - auto if_fshader_src = renderer::resources::ShaderSource( - renderer::resources::shader_lang_t::glsl, - renderer::resources::shader_stage_t::fragment, - if_fshader_file); - - // Shader for rendering the flow field - auto ff_vshader_file = shaderdir / "demo_0_flow_field.vert.glsl"; - auto ff_vshader_src = renderer::resources::ShaderSource( - renderer::resources::shader_lang_t::glsl, - renderer::resources::shader_stage_t::vertex, - ff_vshader_file); - - auto ff_fshader_file = shaderdir / "demo_0_flow_field.frag.glsl"; - auto ff_fshader_src = renderer::resources::ShaderSource( - renderer::resources::shader_lang_t::glsl, - renderer::resources::shader_stage_t::fragment, - ff_fshader_file); - - // Shader for rendering the grid - auto grid_vshader_file = shaderdir / "demo_0_grid.vert.glsl"; - auto grid_vshader_src = renderer::resources::ShaderSource( - renderer::resources::shader_lang_t::glsl, - renderer::resources::shader_stage_t::vertex, - grid_vshader_file); - - auto grid_fshader_file = shaderdir / "demo_0_grid.frag.glsl"; - auto grid_fshader_src = renderer::resources::ShaderSource( - renderer::resources::shader_lang_t::glsl, - renderer::resources::shader_stage_t::fragment, - grid_fshader_file); - - // Shader for monocolored objects - auto obj_vshader_file = shaderdir / "demo_0_obj.vert.glsl"; - auto obj_vshader_src = renderer::resources::ShaderSource( - renderer::resources::shader_lang_t::glsl, - renderer::resources::shader_stage_t::vertex, - obj_vshader_file); - - auto obj_fshader_file = shaderdir / "demo_0_obj.frag.glsl"; - auto obj_fshader_src = renderer::resources::ShaderSource( - renderer::resources::shader_lang_t::glsl, - renderer::resources::shader_stage_t::fragment, - obj_fshader_file); - - // Shader for rendering to the display target - auto display_vshader_file = shaderdir / "demo_0_display.vert.glsl"; - auto display_vshader_src = renderer::resources::ShaderSource( - renderer::resources::shader_lang_t::glsl, - renderer::resources::shader_stage_t::vertex, - display_vshader_file); - - auto display_fshader_file = shaderdir / "demo_0_display.frag.glsl"; - auto display_fshader_src = renderer::resources::ShaderSource( - renderer::resources::shader_lang_t::glsl, - renderer::resources::shader_stage_t::fragment, - display_fshader_file); - - // Create the shaders - auto cf_shader = renderer->add_shader({cf_vshader_src, cf_fshader_src}); - auto if_shader = renderer->add_shader({if_vshader_src, if_fshader_src}); - auto ff_shader = renderer->add_shader({ff_vshader_src, ff_fshader_src}); - auto grid_shader = renderer->add_shader({grid_vshader_src, grid_fshader_src}); - auto obj_shader = renderer->add_shader({obj_vshader_src, obj_fshader_src}); - auto display_shader = renderer->add_shader({display_vshader_src, display_fshader_src}); - - // Make a framebuffer for the background render pass to draw into - auto size = window.get_size(); - auto background_texture = renderer->add_texture( - renderer::resources::Texture2dInfo(size[0], - size[1], - renderer::resources::pixel_format::rgba8)); - auto depth_texture = renderer->add_texture( - renderer::resources::Texture2dInfo(size[0], - size[1], - renderer::resources::pixel_format::depth24)); - auto fbo = renderer->create_texture_target({background_texture, depth_texture}); - auto background_pass = renderer->add_render_pass({}, fbo); - - // Make a framebuffer for the field render passes to draw into - auto field_texture = renderer->add_texture( - renderer::resources::Texture2dInfo(size[0], - size[1], - renderer::resources::pixel_format::rgba8)); - auto depth_texture_2 = renderer->add_texture( - renderer::resources::Texture2dInfo(size[0], - size[1], - renderer::resources::pixel_format::depth24)); - auto field_fbo = renderer->create_texture_target({field_texture, depth_texture_2}); - auto field_pass = renderer->add_render_pass({}, field_fbo); - - // Make a framebuffer for the grid render passes to draw into - auto grid_texture = renderer->add_texture( - renderer::resources::Texture2dInfo(size[0], - size[1], - renderer::resources::pixel_format::rgba8)); - auto depth_texture_3 = renderer->add_texture( - renderer::resources::Texture2dInfo(size[0], - size[1], - renderer::resources::pixel_format::depth24)); - auto grid_fbo = renderer->create_texture_target({grid_texture, depth_texture_3}); - auto grid_pass = renderer->add_render_pass({}, grid_fbo); - - // Make two objects that draw the results of the previous passes onto the screen - // in the display render pass - auto bg_texture_unif = display_shader->new_uniform_input("color_texture", background_texture); - auto quad = renderer->add_mesh_geometry(renderer::resources::MeshData::make_quad()); - renderer::Renderable bg_pass_obj{ - bg_texture_unif, - quad, - true, - true, - }; - auto field_texture_unif = display_shader->new_uniform_input("color_texture", field_texture); - renderer::Renderable field_pass_obj{ - field_texture_unif, - quad, - true, - true, - }; - auto grid_texture_unif = display_shader->new_uniform_input("color_texture", grid_texture); - renderer::Renderable grid_pass_obj{ - grid_texture_unif, - quad, - true, - true, - }; - auto display_pass = renderer->add_render_pass( - {bg_pass_obj, field_pass_obj, grid_pass_obj}, - renderer->get_display_target()); - - // Background object for contrast between field and display - auto background_unifs = obj_shader->new_uniform_input( - "color", - Eigen::Vector4f{64.0 / 256, 128.0 / 256, 196.0 / 256, 1.0}); - auto background = renderer->add_mesh_geometry(renderer::resources::MeshData::make_quad()); - renderer::Renderable background_obj{ - background_unifs, - background, - true, - true, - }; - background_pass->add_renderables(background_obj); +void path_demo_0(const util::Path &path) { + // Side length of the field + // Creates a 10x10 grid + auto field_length = 10; - // Create the pathfinding fields - auto cost_field = std::make_shared(10); + // Cost field with some obstacles + auto cost_field = std::make_shared(field_length); cost_field->set_cost(0, 0, 255); cost_field->set_cost(1, 0, 254); cost_field->set_cost(4, 3, 128); @@ -592,25 +38,30 @@ void path_demo_0(const util::Path &path) { cost_field->set_cost(5, 4, 128); cost_field->set_cost(6, 4, 128); - auto integration_field = std::make_shared(10); + // Create an integration field from the cost field + auto integration_field = std::make_shared(field_length); + + // Set cell (7, 7) to be the target cell integration_field->integrate(cost_field, 7, 7); - auto flow_field = std::make_shared(10); + + // Create a flow field from the integration field + auto flow_field = std::make_shared(field_length); flow_field->build(integration_field); - window.add_mouse_button_callback([&](const QMouseEvent &ev) { + // Render the grid and the pathfinding results + auto qtapp = std::make_shared(); + auto window = std::make_shared("openage pathfinding test", 1440, 720, true); + auto render_manager = std::make_shared(qtapp, window, path); + + // Enable mouse button callbacks + window->add_mouse_button_callback([&](const QMouseEvent &ev) { if (ev.type() == QEvent::MouseButtonRelease) { - if (ev.button() == Qt::RightButton) { - auto grid_plane_normal = Eigen::Vector3f{0, 1, 0}; - auto grid_plane_point = Eigen::Vector3f{0, 0, 0}; - auto camera_direction = renderer::camera::cam_direction; - auto camera_position = camera->get_input_pos( - coord::input{ev.position().x(), ev.position().y()}); - - Eigen::Vector3f intersect = camera_position + camera_direction * (grid_plane_point - camera_position).dot(grid_plane_normal) / camera_direction.dot(grid_plane_normal); - auto grid_x = static_cast(-1 * intersect[2]); - auto grid_y = static_cast(intersect[0]); - - if (grid_x >= 0 && grid_x < 10 && grid_y >= 0 && grid_y < 10) { + if (ev.button() == Qt::RightButton) { // Set target cell + auto tile_pos = render_manager->select_tile(ev.position().x(), ev.position().y()); + auto grid_x = tile_pos.first; + auto grid_y = tile_pos.second; + + if (grid_x >= 0 and grid_x < field_length and grid_y >= 0 and grid_y < field_length) { integration_field->integrate(cost_field, grid_x, grid_y); flow_field->build(integration_field); } @@ -618,167 +69,28 @@ void path_demo_0(const util::Path &path) { } }); - // Create object for the cost field - // this will be shown at the start - Eigen::Matrix4f model = Eigen::Matrix4f::Identity(); - auto cost_field_unifs = cf_shader->new_uniform_input( - "model", - model, - "view", - camera->get_view_matrix(), - "proj", - camera->get_projection_matrix()); - auto cost_field_mesh = get_cost_field_mesh(cost_field); - auto cost_field_geometry = renderer->add_mesh_geometry(cost_field_mesh); - renderer::Renderable cost_field_renderable{ - cost_field_unifs, - cost_field_geometry, - true, - true, - }; - field_pass->add_renderables(cost_field_renderable); - - window.add_key_callback([&](const QKeyEvent &ev) { + window->add_key_callback([&](const QKeyEvent &ev) { if (ev.type() == QEvent::KeyRelease) { if (ev.key() == Qt::Key_F1) { // Show cost field - // Recreate object for the cost field - Eigen::Matrix4f model = Eigen::Matrix4f::Identity(); - auto cost_field_unifs = cf_shader->new_uniform_input( - "model", - model, - "view", - camera->get_view_matrix(), - "proj", - camera->get_projection_matrix()); - auto cost_field_mesh = get_cost_field_mesh(cost_field); - auto cost_field_geometry = renderer->add_mesh_geometry(cost_field_mesh); - renderer::Renderable cost_field_renderable{ - cost_field_unifs, - cost_field_geometry, - true, - true, - }; - field_pass->set_renderables({cost_field_renderable}); + render_manager->show_cost_field(cost_field); } else if (ev.key() == Qt::Key_F2) { // Show integration field - // Create object for the integration field - auto integration_field_unifs = if_shader->new_uniform_input( - "model", - model, - "view", - camera->get_view_matrix(), - "proj", - camera->get_projection_matrix()); - auto integration_field_mesh = get_integration_field_mesh(integration_field, 4); - auto integration_field_geometry = renderer->add_mesh_geometry(integration_field_mesh); - renderer::Renderable integration_field_renderable{ - integration_field_unifs, - integration_field_geometry, - true, - true, - }; - field_pass->set_renderables({integration_field_renderable}); + render_manager->show_integration_field(integration_field); } else if (ev.key() == Qt::Key_F3) { // Show flow field - // Create object for the flow field - auto flow_field_unifs = ff_shader->new_uniform_input( - "model", - model, - "view", - camera->get_view_matrix(), - "proj", - camera->get_projection_matrix(), - "color", - Eigen::Vector4f{192.0 / 256, 255.0 / 256, 64.0 / 256, 1.0}); - auto flow_field_mesh = get_flow_field_mesh(flow_field); - auto flow_field_geometry = renderer->add_mesh_geometry(flow_field_mesh); - renderer::Renderable flow_field_renderable{ - flow_field_unifs, - flow_field_geometry, - true, - true, - }; - field_pass->set_renderables({flow_field_renderable}); + render_manager->show_flow_field(flow_field); } else if (ev.key() == Qt::Key_F4) { // Show steering vectors - static const std::unordered_map offset_dir{ - {flow_dir_t::NORTH, {-0.25f, 0.0f, 0.0f}}, - {flow_dir_t::NORTH_EAST, {-0.25f, 0.0f, -0.25f}}, - {flow_dir_t::EAST, {0.0f, 0.0f, -0.25f}}, - {flow_dir_t::SOUTH_EAST, {0.25f, 0.0f, -0.25f}}, - {flow_dir_t::SOUTH, {0.25f, 0.0f, 0.0f}}, - {flow_dir_t::SOUTH_WEST, {0.25f, 0.0f, 0.25f}}, - {flow_dir_t::WEST, {0.0f, 0.0f, 0.25f}}, - {flow_dir_t::NORTH_WEST, {-0.25f, 0.0f, 0.25f}}, - }; - - for (size_t y = 0; y < flow_field->get_size(); ++y) { - for (size_t x = 0; x < flow_field->get_size(); ++x) { - auto cell = flow_field->get_cell(x, y); - if (cell & FLOW_PATHABLE) { - Eigen::Affine3f arrow_model = Eigen::Affine3f::Identity(); - arrow_model.translate(Eigen::Vector3f(y + 0.5, 0, -1.0f * x - 0.5)); - auto dir = static_cast(cell & FLOW_DIR_MASK); - arrow_model.translate(offset_dir.at(dir)); - - auto rotation_rad = (cell & FLOW_DIR_MASK) * -45 * math::DEGSPERRAD; - arrow_model.rotate(Eigen::AngleAxisf(rotation_rad, Eigen::Vector3f::UnitY())); - auto arrow_unifs = ff_shader->new_uniform_input( - "model", - arrow_model.matrix(), - "view", - camera->get_view_matrix(), - "proj", - camera->get_projection_matrix(), - "color", - Eigen::Vector4f{0.0f, 0.0f, 0.0f, 1.0f}); - auto arrow_mesh = get_arrow_mesh(); - auto arrow_geometry = renderer->add_mesh_geometry(arrow_mesh); - renderer::Renderable arrow_renderable{ - arrow_unifs, - arrow_geometry, - true, - true, - }; - field_pass->add_renderables(arrow_renderable); - } - } - } + render_manager->show_vectors(flow_field); } } }); - // Create object for the grid - auto grid_unifs = grid_shader->new_uniform_input( - "model", - model, - "view", - camera->get_view_matrix(), - "proj", - camera->get_projection_matrix()); - auto grid_mesh = get_grid_mesh(10); - auto grid_geometry = renderer->add_mesh_geometry(grid_mesh); - renderer::Renderable grid_renderable{ - grid_unifs, - grid_geometry, - true, - true, - }; - grid_pass->add_renderables(grid_renderable); - - while (not window.should_close()) { - qtapp->process_events(); + // Show the cost field on startup + render_manager->show_cost_field(cost_field); - renderer->render(background_pass); - renderer->render(field_pass); - renderer->render(grid_pass); - renderer->render(display_pass); - - window.update(); - - renderer->check_error(); - } - window.close(); + // Run the render loop + render_manager->run(); } @@ -879,6 +191,66 @@ void RenderManager::show_flow_field(const std::shared_ptr &fiel this->field_pass->set_renderables({renderable}); } +void RenderManager::show_vectors(const std::shared_ptr &field) { + static const std::unordered_map offset_dir{ + {flow_dir_t::NORTH, {-0.25f, 0.0f, 0.0f}}, + {flow_dir_t::NORTH_EAST, {-0.25f, 0.0f, -0.25f}}, + {flow_dir_t::EAST, {0.0f, 0.0f, -0.25f}}, + {flow_dir_t::SOUTH_EAST, {0.25f, 0.0f, -0.25f}}, + {flow_dir_t::SOUTH, {0.25f, 0.0f, 0.0f}}, + {flow_dir_t::SOUTH_WEST, {0.25f, 0.0f, 0.25f}}, + {flow_dir_t::WEST, {0.0f, 0.0f, 0.25f}}, + {flow_dir_t::NORTH_WEST, {-0.25f, 0.0f, 0.25f}}, + }; + + for (size_t y = 0; y < field->get_size(); ++y) { + for (size_t x = 0; x < field->get_size(); ++x) { + auto cell = field->get_cell(x, y); + if (cell & FLOW_PATHABLE) { + Eigen::Affine3f arrow_model = Eigen::Affine3f::Identity(); + arrow_model.translate(Eigen::Vector3f(y + 0.5, 0, -1.0f * x - 0.5)); + auto dir = static_cast(cell & FLOW_DIR_MASK); + arrow_model.translate(offset_dir.at(dir)); + + auto rotation_rad = (cell & FLOW_DIR_MASK) * -45 * math::DEGSPERRAD; + arrow_model.rotate(Eigen::AngleAxisf(rotation_rad, Eigen::Vector3f::UnitY())); + auto arrow_unifs = this->flow_shader->new_uniform_input( + "model", + arrow_model.matrix(), + "view", + camera->get_view_matrix(), + "proj", + camera->get_projection_matrix(), + "color", + Eigen::Vector4f{0.0f, 0.0f, 0.0f, 1.0f}); + auto arrow_mesh = get_arrow_mesh(); + auto arrow_geometry = renderer->add_mesh_geometry(arrow_mesh); + renderer::Renderable arrow_renderable{ + arrow_unifs, + arrow_geometry, + true, + true, + }; + field_pass->add_renderables(arrow_renderable); + } + } + } +} + +std::pair RenderManager::select_tile(double x, double y) { + auto grid_plane_normal = Eigen::Vector3f{0, 1, 0}; + auto grid_plane_point = Eigen::Vector3f{0, 0, 0}; + auto camera_direction = renderer::camera::cam_direction; + auto camera_position = camera->get_input_pos( + coord::input{x, y}); + + Eigen::Vector3f intersect = camera_position + camera_direction * (grid_plane_point - camera_position).dot(grid_plane_normal) / camera_direction.dot(grid_plane_normal); + auto grid_x = static_cast(-1 * intersect[2]); + auto grid_y = static_cast(intersect[0]); + + return {grid_x, grid_y}; +} + void RenderManager::init_shaders() { // Shader sources auto shaderdir = this->path / "assets" / "test" / "shaders" / "pathfinding"; @@ -983,7 +355,19 @@ void RenderManager::init_passes() { size[1], renderer::resources::pixel_format::depth24)); auto fbo = renderer->create_texture_target({background_texture, depth_texture}); - this->background_pass = renderer->add_render_pass({}, fbo); + + // Background object for contrast between field and display + auto background_unifs = obj_shader->new_uniform_input( + "color", + Eigen::Vector4f{64.0 / 256, 128.0 / 256, 196.0 / 256, 1.0}); + auto background = renderer->add_mesh_geometry(renderer::resources::MeshData::make_quad()); + renderer::Renderable background_obj{ + background_unifs, + background, + true, + true, + }; + this->background_pass = renderer->add_render_pass({background_obj}, fbo); // Make a framebuffer for the field render passes to draw into auto field_texture = renderer->add_texture( @@ -1007,7 +391,25 @@ void RenderManager::init_passes() { size[1], renderer::resources::pixel_format::depth24)); auto grid_fbo = renderer->create_texture_target({grid_texture, depth_texture_3}); - this->grid_pass = renderer->add_render_pass({}, grid_fbo); + + // Create object for the grid + Eigen::Matrix4f model = Eigen::Matrix4f::Identity(); + auto grid_unifs = grid_shader->new_uniform_input( + "model", + model, + "view", + camera->get_view_matrix(), + "proj", + camera->get_projection_matrix()); + auto grid_mesh = get_grid_mesh(10); + auto grid_geometry = renderer->add_mesh_geometry(grid_mesh); + renderer::Renderable grid_obj{ + grid_unifs, + grid_geometry, + true, + true, + }; + this->grid_pass = renderer->add_render_pass({grid_obj}, grid_fbo); // Make two objects that draw the results of the previous passes onto the screen // in the display render pass diff --git a/libopenage/pathfinding/demo/demo_0.h b/libopenage/pathfinding/demo/demo_0.h index 4ab4a8d327..2e31055f45 100644 --- a/libopenage/pathfinding/demo/demo_0.h +++ b/libopenage/pathfinding/demo/demo_0.h @@ -47,21 +47,77 @@ namespace tests { void path_demo_0(const util::Path &path); +/** + * Manages the graphical display of the pathfinding demo. + */ class RenderManager { public: + /** + * Create a new render manager. + * + * @param app GUI application. + * @param window Window to render to. + * @param path Path to the project rootdir. + */ RenderManager(const std::shared_ptr &app, const std::shared_ptr &window, const util::Path &path); ~RenderManager() = default; + /** + * Run the render loop. + */ void run(); + /** + * Draw a cost field to the screen. + * + * @param field Cost field. + */ void show_cost_field(const std::shared_ptr &field); + + /** + * Draw an integration field to the screen. + * + * @param field Integration field. + */ void show_integration_field(const std::shared_ptr &field); + + /** + * Draw a flow field to the screen. + * + * @param field Flow field. + */ void show_flow_field(const std::shared_ptr &field); + /** + * Draw the steering vectors of a flow field to the screen. + * + * @param field Flow field. + */ + void show_vectors(const std::shared_ptr &field); + + /** + * Get the cell coordinates at a given screen position. + * + * @param x X coordinate. + * @param y Y coordinate. + */ + std::pair select_tile(double x, double y); + private: + /** + * Load the shader sources for the demo and create the shader programs. + */ void init_shaders(); + + /** + * Create the following render passes for the demo: + * - Background pass: Mono-colored background object. + * - Field pass; Renders the cost, integration and flow fields. + * - Grid pass: Renders a grid on top of the fields. + * - Display pass: Draws the results of previous passes to the screen. + */ void init_passes(); @@ -113,24 +169,85 @@ class RenderManager { */ static renderer::resources::MeshData get_grid_mesh(size_t side_length); - + /** + * Path to the project rootdir. + */ const util::Path &path; + /* Renderer objects */ + + /** + * Qt GUI application. + */ std::shared_ptr app; + + /** + * openage window to render to. + */ std::shared_ptr window; + + /** + * openage renderer instance. + */ std::shared_ptr renderer; + + /** + * Camera to view the scene. + */ std::shared_ptr camera; + /* Shader programs */ + + /** + * Shader program for rendering a cost field. + */ std::shared_ptr cost_shader; + + /** + * Shader program for rendering a integration field. + */ std::shared_ptr integration_shader; + + /** + * Shader program for rendering a flow field. + */ std::shared_ptr flow_shader; + + /** + * Shader program for rendering a grid. + */ std::shared_ptr grid_shader; + + /** + * Shader program for rendering mono-colored objects. + */ std::shared_ptr obj_shader; + + /** + * Shader program for rendering the final display. + */ std::shared_ptr display_shader; + /* Render passes */ + + /** + * Background pass: Mono-colored background object. + */ std::shared_ptr background_pass; + + /** + * Field pass: Renders the cost, integration and flow fields. + */ std::shared_ptr field_pass; + + /** + * Grid pass: Renders a grid on top of the fields. + */ std::shared_ptr grid_pass; + + /** + * Display pass: Draws the results of previous passes to the screen. + */ std::shared_ptr display_pass; }; From e3005a8de83bec29a50bacbf51c49c45e3c0ea49 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 25 Feb 2024 01:32:41 +0100 Subject: [PATCH 028/112] path: Toggle vsibility of steering vectors. --- libopenage/pathfinding/demo/demo_0.cpp | 48 ++++++++++++++++++++++---- libopenage/pathfinding/demo/demo_0.h | 10 ++++++ 2 files changed, 52 insertions(+), 6 deletions(-) diff --git a/libopenage/pathfinding/demo/demo_0.cpp b/libopenage/pathfinding/demo/demo_0.cpp index 796c1cc97e..e31262101e 100644 --- a/libopenage/pathfinding/demo/demo_0.cpp +++ b/libopenage/pathfinding/demo/demo_0.cpp @@ -69,6 +69,10 @@ void path_demo_0(const util::Path &path) { } }); + // Make steering vector visibility toggleable + auto vectors_visible = false; + + // Enable key callbacks window->add_key_callback([&](const QKeyEvent &ev) { if (ev.type() == QEvent::KeyRelease) { if (ev.key() == Qt::Key_F1) { // Show cost field @@ -81,7 +85,14 @@ void path_demo_0(const util::Path &path) { render_manager->show_flow_field(flow_field); } else if (ev.key() == Qt::Key_F4) { // Show steering vectors - render_manager->show_vectors(flow_field); + if (vectors_visible) { + render_manager->hide_vectors(); + vectors_visible = false; + } + else { + render_manager->show_vectors(flow_field); + vectors_visible = true; + } } } }); @@ -119,6 +130,7 @@ void RenderManager::run() { this->renderer->render(this->background_pass); this->renderer->render(this->field_pass); + this->renderer->render(this->vector_pass); this->renderer->render(this->grid_pass); this->renderer->render(this->display_pass); @@ -203,6 +215,7 @@ void RenderManager::show_vectors(const std::shared_ptr &field) {flow_dir_t::NORTH_WEST, {-0.25f, 0.0f, 0.25f}}, }; + this->vector_pass->clear_renderables(); for (size_t y = 0; y < field->get_size(); ++y) { for (size_t x = 0; x < field->get_size(); ++x) { auto cell = field->get_cell(x, y); @@ -231,12 +244,16 @@ void RenderManager::show_vectors(const std::shared_ptr &field) true, true, }; - field_pass->add_renderables(arrow_renderable); + this->vector_pass->add_renderables(arrow_renderable); } } } } +void RenderManager::hide_vectors() { + this->vector_pass->clear_renderables(); +} + std::pair RenderManager::select_tile(double x, double y) { auto grid_plane_normal = Eigen::Vector3f{0, 1, 0}; auto grid_plane_point = Eigen::Vector3f{0, 0, 0}; @@ -369,7 +386,7 @@ void RenderManager::init_passes() { }; this->background_pass = renderer->add_render_pass({background_obj}, fbo); - // Make a framebuffer for the field render passes to draw into + // Make a framebuffer for the field render pass to draw into auto field_texture = renderer->add_texture( renderer::resources::Texture2dInfo(size[0], size[1], @@ -381,16 +398,28 @@ void RenderManager::init_passes() { auto field_fbo = renderer->create_texture_target({field_texture, depth_texture_2}); this->field_pass = renderer->add_render_pass({}, field_fbo); + // Make a framebuffer for the vector render passes to draw into + auto vector_texture = renderer->add_texture( + renderer::resources::Texture2dInfo(size[0], + size[1], + renderer::resources::pixel_format::rgba8)); + auto depth_texture_3 = renderer->add_texture( + renderer::resources::Texture2dInfo(size[0], + size[1], + renderer::resources::pixel_format::depth24)); + auto vector_fbo = renderer->create_texture_target({vector_texture, depth_texture_3}); + this->vector_pass = renderer->add_render_pass({}, vector_fbo); + // Make a framebuffer for the grid render passes to draw into auto grid_texture = renderer->add_texture( renderer::resources::Texture2dInfo(size[0], size[1], renderer::resources::pixel_format::rgba8)); - auto depth_texture_3 = renderer->add_texture( + auto depth_texture_4 = renderer->add_texture( renderer::resources::Texture2dInfo(size[0], size[1], renderer::resources::pixel_format::depth24)); - auto grid_fbo = renderer->create_texture_target({grid_texture, depth_texture_3}); + auto grid_fbo = renderer->create_texture_target({grid_texture, depth_texture_4}); // Create object for the grid Eigen::Matrix4f model = Eigen::Matrix4f::Identity(); @@ -428,6 +457,13 @@ void RenderManager::init_passes() { true, true, }; + auto vector_texture_unif = display_shader->new_uniform_input("color_texture", vector_texture); + renderer::Renderable vector_pass_obj{ + vector_texture_unif, + quad, + true, + true, + }; auto grid_texture_unif = display_shader->new_uniform_input("color_texture", grid_texture); renderer::Renderable grid_pass_obj{ grid_texture_unif, @@ -436,7 +472,7 @@ void RenderManager::init_passes() { true, }; this->display_pass = renderer->add_render_pass( - {bg_pass_obj, field_pass_obj, grid_pass_obj}, + {bg_pass_obj, field_pass_obj, vector_pass_obj, grid_pass_obj}, renderer->get_display_target()); } diff --git a/libopenage/pathfinding/demo/demo_0.h b/libopenage/pathfinding/demo/demo_0.h index 2e31055f45..19a0f9858d 100644 --- a/libopenage/pathfinding/demo/demo_0.h +++ b/libopenage/pathfinding/demo/demo_0.h @@ -97,6 +97,11 @@ class RenderManager { */ void show_vectors(const std::shared_ptr &field); + /** + * Hide drawn steering vectors. + */ + void hide_vectors(); + /** * Get the cell coordinates at a given screen position. * @@ -240,6 +245,11 @@ class RenderManager { */ std::shared_ptr field_pass; + /** + * Vector pass: Renders the steering vectors of a flow field. + */ + std::shared_ptr vector_pass; + /** * Grid pass: Renders a grid on top of the fields. */ From 94a423ee678dc930c5828eaf841fe26acb598f0c Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 25 Feb 2024 03:44:03 +0100 Subject: [PATCH 029/112] path: Change flow field tile colors depending on flags. --- .../pathfinding/demo_0_flow_field.frag.glsl | 17 ++++++++++++-- .../pathfinding/demo_0_flow_field.vert.glsl | 4 ++++ .../pathfinding/demo_0_vector.frag.glsl | 9 ++++++++ .../pathfinding/demo_0_vector.vert.glsl | 11 ++++++++++ libopenage/pathfinding/demo/demo_0.cpp | 22 ++++++++++++++----- libopenage/pathfinding/demo/demo_0.h | 7 +++++- 6 files changed, 62 insertions(+), 8 deletions(-) create mode 100644 assets/test/shaders/pathfinding/demo_0_vector.frag.glsl create mode 100644 assets/test/shaders/pathfinding/demo_0_vector.vert.glsl diff --git a/assets/test/shaders/pathfinding/demo_0_flow_field.frag.glsl b/assets/test/shaders/pathfinding/demo_0_flow_field.frag.glsl index ebbb10bc8f..51f4a66804 100644 --- a/assets/test/shaders/pathfinding/demo_0_flow_field.frag.glsl +++ b/assets/test/shaders/pathfinding/demo_0_flow_field.frag.glsl @@ -1,9 +1,22 @@ #version 330 -uniform vec4 color; +in float v_cost; out vec4 outcol; void main() { - outcol = color; + int cost = int(v_cost); + if (!bool(cost & 0x10)) { + // not pathable + outcol = vec4(0.0, 0.0, 0.0, 1.0); + return; + } + + if (bool(cost & 0x20)) { + // line of sight + outcol = vec4(1.0, 1.0, 1.0, 1.0); + return; + } + + outcol = vec4(0.7, 0.7, 0.7, 1.0); } diff --git a/assets/test/shaders/pathfinding/demo_0_flow_field.vert.glsl b/assets/test/shaders/pathfinding/demo_0_flow_field.vert.glsl index 8b6b015408..2c2190e57c 100644 --- a/assets/test/shaders/pathfinding/demo_0_flow_field.vert.glsl +++ b/assets/test/shaders/pathfinding/demo_0_flow_field.vert.glsl @@ -1,11 +1,15 @@ #version 330 layout(location=0) in vec3 position; +layout(location=1) in float cost; uniform mat4 model; uniform mat4 view; uniform mat4 proj; +out float v_cost; + void main() { gl_Position = proj * view * model * vec4(position, 1.0); + v_cost = cost; } diff --git a/assets/test/shaders/pathfinding/demo_0_vector.frag.glsl b/assets/test/shaders/pathfinding/demo_0_vector.frag.glsl new file mode 100644 index 0000000000..ebbb10bc8f --- /dev/null +++ b/assets/test/shaders/pathfinding/demo_0_vector.frag.glsl @@ -0,0 +1,9 @@ +#version 330 + +uniform vec4 color; + +out vec4 outcol; + +void main() { + outcol = color; +} diff --git a/assets/test/shaders/pathfinding/demo_0_vector.vert.glsl b/assets/test/shaders/pathfinding/demo_0_vector.vert.glsl new file mode 100644 index 0000000000..8b6b015408 --- /dev/null +++ b/assets/test/shaders/pathfinding/demo_0_vector.vert.glsl @@ -0,0 +1,11 @@ +#version 330 + +layout(location=0) in vec3 position; + +uniform mat4 model; +uniform mat4 view; +uniform mat4 proj; + +void main() { + gl_Position = proj * view * model * vec4(position, 1.0); +} diff --git a/libopenage/pathfinding/demo/demo_0.cpp b/libopenage/pathfinding/demo/demo_0.cpp index e31262101e..619246ddec 100644 --- a/libopenage/pathfinding/demo/demo_0.cpp +++ b/libopenage/pathfinding/demo/demo_0.cpp @@ -189,9 +189,7 @@ void RenderManager::show_flow_field(const std::shared_ptr &fiel "view", this->camera->get_view_matrix(), "proj", - this->camera->get_projection_matrix(), - "color", - Eigen::Vector4f{192.0 / 256, 255.0 / 256, 64.0 / 256, 1.0}); + this->camera->get_projection_matrix()); auto mesh = get_flow_field_mesh(field); auto geometry = this->renderer->add_mesh_geometry(mesh); renderer::Renderable renderable{ @@ -227,7 +225,7 @@ void RenderManager::show_vectors(const std::shared_ptr &field) auto rotation_rad = (cell & FLOW_DIR_MASK) * -45 * math::DEGSPERRAD; arrow_model.rotate(Eigen::AngleAxisf(rotation_rad, Eigen::Vector3f::UnitY())); - auto arrow_unifs = this->flow_shader->new_uniform_input( + auto arrow_unifs = this->vector_shader->new_uniform_input( "model", arrow_model.matrix(), "view", @@ -311,6 +309,19 @@ void RenderManager::init_shaders() { renderer::resources::shader_stage_t::fragment, ff_fshader_file); + // Shader for rendering steering vectors + auto vec_vshader_file = shaderdir / "demo_0_vector.vert.glsl"; + auto vec_vshader_src = renderer::resources::ShaderSource( + renderer::resources::shader_lang_t::glsl, + renderer::resources::shader_stage_t::vertex, + vec_vshader_file); + + auto vec_fshader_file = shaderdir / "demo_0_vector.frag.glsl"; + auto vec_fshader_src = renderer::resources::ShaderSource( + renderer::resources::shader_lang_t::glsl, + renderer::resources::shader_stage_t::fragment, + vec_fshader_file); + // Shader for rendering the grid auto grid_vshader_file = shaderdir / "demo_0_grid.vert.glsl"; auto grid_vshader_src = renderer::resources::ShaderSource( @@ -324,7 +335,7 @@ void RenderManager::init_shaders() { renderer::resources::shader_stage_t::fragment, grid_fshader_file); - // Shader for monocolored objects + // Shader for 2D monocolored objects auto obj_vshader_file = shaderdir / "demo_0_obj.vert.glsl"; auto obj_vshader_src = renderer::resources::ShaderSource( renderer::resources::shader_lang_t::glsl, @@ -354,6 +365,7 @@ void RenderManager::init_shaders() { this->cost_shader = renderer->add_shader({cf_vshader_src, cf_fshader_src}); this->integration_shader = renderer->add_shader({if_vshader_src, if_fshader_src}); this->flow_shader = renderer->add_shader({ff_vshader_src, ff_fshader_src}); + this->vector_shader = renderer->add_shader({vec_vshader_src, vec_fshader_src}); this->grid_shader = renderer->add_shader({grid_vshader_src, grid_fshader_src}); this->obj_shader = renderer->add_shader({obj_vshader_src, obj_fshader_src}); this->display_shader = renderer->add_shader({display_vshader_src, display_fshader_src}); diff --git a/libopenage/pathfinding/demo/demo_0.h b/libopenage/pathfinding/demo/demo_0.h index 19a0f9858d..90874830d7 100644 --- a/libopenage/pathfinding/demo/demo_0.h +++ b/libopenage/pathfinding/demo/demo_0.h @@ -218,13 +218,18 @@ class RenderManager { */ std::shared_ptr flow_shader; + /** + * Shader program for rendering steering vectors. + */ + std::shared_ptr vector_shader; + /** * Shader program for rendering a grid. */ std::shared_ptr grid_shader; /** - * Shader program for rendering mono-colored objects. + * Shader program for rendering 2D mono-colored objects. */ std::shared_ptr obj_shader; From ea1a01da81ee170edec1655c5d420dbe2c51c15d Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 25 Feb 2024 03:50:58 +0100 Subject: [PATCH 030/112] path: Update rendering when switching targets. --- libopenage/pathfinding/demo/demo_0.cpp | 34 +++++++++++++++++++++----- libopenage/pathfinding/demo/demo_0.h | 6 +++++ 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/libopenage/pathfinding/demo/demo_0.cpp b/libopenage/pathfinding/demo/demo_0.cpp index 619246ddec..8c3a1769ff 100644 --- a/libopenage/pathfinding/demo/demo_0.cpp +++ b/libopenage/pathfinding/demo/demo_0.cpp @@ -53,6 +53,13 @@ void path_demo_0(const util::Path &path) { auto window = std::make_shared("openage pathfinding test", 1440, 720, true); auto render_manager = std::make_shared(qtapp, window, path); + // Show the cost field on startup + render_manager->show_cost_field(cost_field); + auto current_field = RenderManager::field_t::COST; + + // Make steering vector visibility toggleable + auto vectors_visible = false; + // Enable mouse button callbacks window->add_mouse_button_callback([&](const QMouseEvent &ev) { if (ev.type() == QEvent::MouseButtonRelease) { @@ -62,27 +69,45 @@ void path_demo_0(const util::Path &path) { auto grid_y = tile_pos.second; if (grid_x >= 0 and grid_x < field_length and grid_y >= 0 and grid_y < field_length) { + // Recalculate the integration field and the flow field integration_field->integrate(cost_field, grid_x, grid_y); flow_field->build(integration_field); + + // Show the new field values and vectors + switch (current_field) { + case RenderManager::field_t::COST: + render_manager->show_cost_field(cost_field); + break; + case RenderManager::field_t::INTEGRATION: + render_manager->show_integration_field(integration_field); + break; + case RenderManager::field_t::FLOW: + render_manager->show_flow_field(flow_field); + break; + } + + if (vectors_visible) { + render_manager->show_vectors(flow_field); + } } } } }); - // Make steering vector visibility toggleable - auto vectors_visible = false; - // Enable key callbacks window->add_key_callback([&](const QKeyEvent &ev) { if (ev.type() == QEvent::KeyRelease) { if (ev.key() == Qt::Key_F1) { // Show cost field render_manager->show_cost_field(cost_field); + current_field = RenderManager::field_t::COST; } else if (ev.key() == Qt::Key_F2) { // Show integration field render_manager->show_integration_field(integration_field); + current_field = RenderManager::field_t::INTEGRATION; } else if (ev.key() == Qt::Key_F3) { // Show flow field render_manager->show_flow_field(flow_field); + current_field = RenderManager::field_t::FLOW; } else if (ev.key() == Qt::Key_F4) { // Show steering vectors if (vectors_visible) { @@ -97,9 +122,6 @@ void path_demo_0(const util::Path &path) { } }); - // Show the cost field on startup - render_manager->show_cost_field(cost_field); - // Run the render loop render_manager->run(); } diff --git a/libopenage/pathfinding/demo/demo_0.h b/libopenage/pathfinding/demo/demo_0.h index 90874830d7..0dcd115c0e 100644 --- a/libopenage/pathfinding/demo/demo_0.h +++ b/libopenage/pathfinding/demo/demo_0.h @@ -52,6 +52,12 @@ void path_demo_0(const util::Path &path); */ class RenderManager { public: + enum class field_t { + COST, + INTEGRATION, + FLOW + }; + /** * Create a new render manager. * From 164ed608895e1616fa461490495b87663f34419c Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 25 Feb 2024 20:58:08 +0100 Subject: [PATCH 031/112] path: Fix wavefront algorithm. Cells can now be visited multiple times if their costs change again during integration steps. --- libopenage/pathfinding/integration_field.cpp | 108 ++++++++----------- libopenage/pathfinding/integration_field.h | 18 ++-- 2 files changed, 56 insertions(+), 70 deletions(-) diff --git a/libopenage/pathfinding/integration_field.cpp b/libopenage/pathfinding/integration_field.cpp index ee0a91d96f..eb530374e4 100644 --- a/libopenage/pathfinding/integration_field.cpp +++ b/libopenage/pathfinding/integration_field.cpp @@ -2,9 +2,6 @@ #include "integration_field.h" -#include -#include - #include "error/error.h" #include "pathfinding/cost_field.h" @@ -13,36 +10,6 @@ namespace openage::path { -/** - * Update the open list for a neighbour index during integration field - * calculation (subroutine of integrate(..)). - * - * Checks whether: - * - * 1. the neighbour index has not already been found - * 2. the neighbour index is reachable - * - * If both conditions are met, the neighbour index is added to the open list. - * - * @param idx Index of the cell to update. - * @param integrate_cost Integrated cost of the cell. - * @param open_list Cells that still have to be visited. - * @param found Cells that have been found. - */ -void update_list(size_t idx, - integrate_t integrate_cost, - std::deque &open_list, - std::unordered_set &found) { - if (not found.contains(idx)) { - found.insert(idx); - - if (integrate_cost != INTEGRATE_UNREACHABLE) { - open_list.push_back(idx); - } - } -} - - IntegrationField::IntegrationField(size_t size) : size{size}, cells(this->size * this->size, INTEGRATE_UNREACHABLE) { @@ -75,12 +42,18 @@ void IntegrationField::integrate(const std::shared_ptr &cost_field, // Target cell index auto target_idx = target_x + target_y * this->size; - // Cells that have been found - std::unordered_set found; - found.reserve(this->size * this->size); + // Lookup table for cells that are in the open list + std::unordered_set in_list; + in_list.reserve(this->size * this->size); + // Cells that still have to be visited + // they may be visited multiple times std::deque open_list; + // Stores neighbors of the current cell + std::vector neighbors; + neighbors.reserve(4); + // Move outwards from the target cell, updating the integration field this->cells[target_idx] = INTEGRATE_START; open_list.push_back(target_idx); @@ -94,35 +67,34 @@ void IntegrationField::integrate(const std::shared_ptr &cost_field, auto integrated_current = this->cells[idx]; - // Update the integration field of the 4 neighbouring cells + // Get the neighbors of the current cell if (y > 0) { - auto up_idx = idx - this->size; - auto neighbor_cost = this->update_cell(up_idx, - cost_field->get_cost(up_idx), - integrated_current); - update_list(up_idx, neighbor_cost, open_list, found); + neighbors.push_back(idx - this->size); } if (x > 0) { - auto left_idx = idx - 1; - auto neighbor_cost = this->update_cell(left_idx, - cost_field->get_cost(left_idx), - integrated_current); - update_list(left_idx, neighbor_cost, open_list, found); + neighbors.push_back(idx - 1); } if (y < this->size - 1) { - auto down_idx = idx + this->size; - auto neighbor_cost = this->update_cell(down_idx, - cost_field->get_cost(down_idx), - integrated_current); - update_list(down_idx, neighbor_cost, open_list, found); + neighbors.push_back(idx + this->size); } if (x < this->size - 1) { - auto right_idx = idx + 1; - auto neighbor_cost = this->update_cell(right_idx, - cost_field->get_cost(right_idx), - integrated_current); - update_list(right_idx, neighbor_cost, open_list, found); + neighbors.push_back(idx + 1); } + + // Update the integration field of the neighboring cells + for (auto &neighbor_idx : neighbors) { + this->update_neighbor(neighbor_idx, + cost_field->get_cost(neighbor_idx), + integrated_current, + open_list, + in_list); + } + + // Clear the neighbors vector + neighbors.clear(); + + // Remove the current cell from the open list lookup table + in_list.erase(idx); } } @@ -136,23 +108,33 @@ void IntegrationField::reset() { } } -integrate_t IntegrationField::update_cell(size_t idx, - cost_t cell_cost, - integrate_t integrate_cost) { +integrate_t IntegrationField::update_neighbor(size_t idx, + cost_t cell_cost, + integrate_t integrated_cost, + std::deque &open_list, + std::unordered_set &in_list) { ENSURE(cell_cost > COST_INIT, "cost field cell value must be non-zero"); - // Check if the cell is impassable. + // Check if the cell is impassable + // then we don't need to update the integration field if (cell_cost == COST_IMPASSABLE) { return INTEGRATE_UNREACHABLE; } - // Update the integration field value of the cell. - auto integrated = integrate_cost + cell_cost; + auto integrated = integrated_cost + cell_cost; if (integrated < this->cells.at(idx)) { + // If the new integration value is smaller than the current one, + // update the cell and add it to the open list this->cells[idx] = integrated; + + if (not in_list.contains(idx)) { + in_list.insert(idx); + open_list.push_back(idx); + } } return integrated; } + } // namespace openage::path diff --git a/libopenage/pathfinding/integration_field.h b/libopenage/pathfinding/integration_field.h index fc83683a30..1860c6cfad 100644 --- a/libopenage/pathfinding/integration_field.h +++ b/libopenage/pathfinding/integration_field.h @@ -3,7 +3,9 @@ #pragma once #include +#include #include +#include #include #include "pathfinding/types.h" @@ -73,17 +75,19 @@ class IntegrationField { void reset(); /** - * Update a cell in the integration field. + * Update a neigbor cell during the integration process. * - * @param idx Index of the cell that is updated. - * @param cell_cost Cost of the cell from the cost field. - * @param integrate_cost Integrated cost of the updating cell in the integration field. + * @param idx Index of the neighbor cell that is updated. + * @param cell_cost Cost of the neighbor cell from the cost field. + * @param integrated_cost Current integrated cost of the updating cell in the integration field. * * @return New integration value of the cell. */ - integrate_t update_cell(size_t idx, - cost_t cell_cost, - integrate_t integrate_cost); + integrate_t update_neighbor(size_t idx, + cost_t cell_cost, + integrate_t integrated_cost, + std::deque &open_list, + std::unordered_set &in_list); /** * Side length of the field. From 2800ac8df2745277e72f81b17356b2777c7d9537 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 25 Feb 2024 22:23:02 +0100 Subject: [PATCH 032/112] path: Turn off OpenGL debugging for demo. --- libopenage/pathfinding/demo/demo_0.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libopenage/pathfinding/demo/demo_0.cpp b/libopenage/pathfinding/demo/demo_0.cpp index 8c3a1769ff..04e70680ef 100644 --- a/libopenage/pathfinding/demo/demo_0.cpp +++ b/libopenage/pathfinding/demo/demo_0.cpp @@ -50,7 +50,7 @@ void path_demo_0(const util::Path &path) { // Render the grid and the pathfinding results auto qtapp = std::make_shared(); - auto window = std::make_shared("openage pathfinding test", 1440, 720, true); + auto window = std::make_shared("openage pathfinding test", 1440, 720); auto render_manager = std::make_shared(qtapp, window, path); // Show the cost field on startup From 1746590e488d0cf9899fed934f2b8d50c297531b Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 25 Feb 2024 22:31:09 +0100 Subject: [PATCH 033/112] path: Add helpful log messages to demo. --- libopenage/pathfinding/demo/demo_0.cpp | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/libopenage/pathfinding/demo/demo_0.cpp b/libopenage/pathfinding/demo/demo_0.cpp index 04e70680ef..b2c5476127 100644 --- a/libopenage/pathfinding/demo/demo_0.cpp +++ b/libopenage/pathfinding/demo/demo_0.cpp @@ -37,25 +37,33 @@ void path_demo_0(const util::Path &path) { cost_field->set_cost(4, 4, 128); cost_field->set_cost(5, 4, 128); cost_field->set_cost(6, 4, 128); + log::log(INFO << "Created cost field"); // Create an integration field from the cost field auto integration_field = std::make_shared(field_length); + log::log(INFO << "Created integration field"); // Set cell (7, 7) to be the target cell integration_field->integrate(cost_field, 7, 7); + log::log(INFO << "Calculated integration field for target cell (7, 7)"); // Create a flow field from the integration field auto flow_field = std::make_shared(field_length); + log::log(INFO << "Created flow field"); + flow_field->build(integration_field); + log::log(INFO << "Built flow field from integration field"); // Render the grid and the pathfinding results auto qtapp = std::make_shared(); auto window = std::make_shared("openage pathfinding test", 1440, 720); auto render_manager = std::make_shared(qtapp, window, path); + log::log(INFO << "Created RenderManager for pathfinding demo"); // Show the cost field on startup render_manager->show_cost_field(cost_field); auto current_field = RenderManager::field_t::COST; + log::log(INFO << "Showing cost field"); // Make steering vector visibility toggleable auto vectors_visible = false; @@ -69,9 +77,15 @@ void path_demo_0(const util::Path &path) { auto grid_y = tile_pos.second; if (grid_x >= 0 and grid_x < field_length and grid_y >= 0 and grid_y < field_length) { + log::log(INFO << "Selected new target cell (" << grid_x << ", " << grid_y << ")"); + // Recalculate the integration field and the flow field integration_field->integrate(cost_field, grid_x, grid_y); + log::log(INFO << "Calculated integration field for target cell (" + << grid_x << ", " << grid_y << ")"); + flow_field->build(integration_field); + log::log(INFO << "Built flow field from integration field"); // Show the new field values and vectors switch (current_field) { @@ -100,28 +114,37 @@ void path_demo_0(const util::Path &path) { if (ev.key() == Qt::Key_F1) { // Show cost field render_manager->show_cost_field(cost_field); current_field = RenderManager::field_t::COST; + log::log(INFO << "Showing cost field"); } else if (ev.key() == Qt::Key_F2) { // Show integration field render_manager->show_integration_field(integration_field); current_field = RenderManager::field_t::INTEGRATION; + log::log(INFO << "Showing integration field"); } else if (ev.key() == Qt::Key_F3) { // Show flow field render_manager->show_flow_field(flow_field); current_field = RenderManager::field_t::FLOW; + log::log(INFO << "Showing flow field"); } else if (ev.key() == Qt::Key_F4) { // Show steering vectors if (vectors_visible) { render_manager->hide_vectors(); vectors_visible = false; + log::log(INFO << "Hiding steering vectors"); } else { render_manager->show_vectors(flow_field); vectors_visible = true; + log::log(INFO << "Showing steering vectors"); } } } }); + log::log(INFO << "Instructions:"); + log::log(INFO << " 1. Press F1/F2/F3 to show cost/integration/flow field"); + log::log(INFO << " 2. Press F4 to toggle steering vectors"); + // Run the render loop render_manager->run(); } From 88fd1f11eff9049b049d5b29211768c7f0253ca1 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 25 Feb 2024 22:37:12 +0100 Subject: [PATCH 034/112] path: Add log messages for field types. --- libopenage/pathfinding/cost_field.cpp | 2 ++ libopenage/pathfinding/flow_field.cpp | 4 ++++ libopenage/pathfinding/integration_field.cpp | 3 +++ 3 files changed, 9 insertions(+) diff --git a/libopenage/pathfinding/cost_field.cpp b/libopenage/pathfinding/cost_field.cpp index d00f51decf..ad19f8b910 100644 --- a/libopenage/pathfinding/cost_field.cpp +++ b/libopenage/pathfinding/cost_field.cpp @@ -3,6 +3,7 @@ #include "cost_field.h" #include "error/error.h" +#include "log/log.h" #include "pathfinding/definitions.h" @@ -12,6 +13,7 @@ namespace openage::path { CostField::CostField(size_t size) : size{size}, cells(this->size * this->size, COST_MIN) { + log::log(DBG << "Created cost field with size " << this->size << "x" << this->size); } size_t CostField::get_size() const { diff --git a/libopenage/pathfinding/flow_field.cpp b/libopenage/pathfinding/flow_field.cpp index 63d7bca860..36487d4613 100644 --- a/libopenage/pathfinding/flow_field.cpp +++ b/libopenage/pathfinding/flow_field.cpp @@ -3,6 +3,7 @@ #include "flow_field.h" #include "error/error.h" +#include "log/log.h" #include "pathfinding/integration_field.h" @@ -12,6 +13,7 @@ namespace openage::path { FlowField::FlowField(size_t size) : size{size}, cells(this->size * this->size, 0) { + log::log(DBG << "Created flow field with size " << this->size << "x" << this->size); } FlowField::FlowField(const std::shared_ptr &integrate_field) : @@ -131,6 +133,8 @@ void FlowField::reset() { for (auto &cell : this->cells) { cell = 0; } + + log::log(DBG << "Flow field has been reset"); } } // namespace openage::path diff --git a/libopenage/pathfinding/integration_field.cpp b/libopenage/pathfinding/integration_field.cpp index eb530374e4..aef6fc7594 100644 --- a/libopenage/pathfinding/integration_field.cpp +++ b/libopenage/pathfinding/integration_field.cpp @@ -3,6 +3,7 @@ #include "integration_field.h" #include "error/error.h" +#include "log/log.h" #include "pathfinding/cost_field.h" #include "pathfinding/definitions.h" @@ -13,6 +14,7 @@ namespace openage::path { IntegrationField::IntegrationField(size_t size) : size{size}, cells(this->size * this->size, INTEGRATE_UNREACHABLE) { + log::log(DBG << "Created integration field with size " << this->size << "x" << this->size); } size_t IntegrationField::get_size() const { @@ -106,6 +108,7 @@ void IntegrationField::reset() { for (auto &cell : this->cells) { cell = INTEGRATE_UNREACHABLE; } + log::log(DBG << "Integration field has been reset"); } integrate_t IntegrationField::update_neighbor(size_t idx, From 23d8298a197756fda9aef6882653623c3ad205e7 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 25 Feb 2024 22:39:34 +0100 Subject: [PATCH 035/112] path: Add constant for initial flow field cell value. --- libopenage/pathfinding/definitions.h | 9 +++++++-- libopenage/pathfinding/demo/demo_0.cpp | 2 +- libopenage/pathfinding/flow_field.cpp | 8 ++++---- libopenage/pathfinding/tests.cpp | 16 ++++++++-------- 4 files changed, 20 insertions(+), 15 deletions(-) diff --git a/libopenage/pathfinding/definitions.h b/libopenage/pathfinding/definitions.h index ea8936d809..5756ac8c61 100644 --- a/libopenage/pathfinding/definitions.h +++ b/libopenage/pathfinding/definitions.h @@ -59,6 +59,11 @@ enum class flow_dir_t : uint8_t { NORTH_WEST = 0x07, }; +/** + * Initial value for a flow field cell. + */ +constexpr flow_t FLOW_INIT = 0; + /** * Mask for the flow direction bits in a flow_t value. */ @@ -67,11 +72,11 @@ constexpr flow_t FLOW_DIR_MASK = 0x0F; /** * Mask for the pathable flag in a flow_t value. */ -constexpr flow_t FLOW_PATHABLE = 0x10; +constexpr flow_t FLOW_PATHABLE_MASK = 0x10; /** * Mask for the line of sight flag in a flow_t value. */ -constexpr flow_t FLOW_LOS = 0x20; +constexpr flow_t FLOW_LOS_MASK = 0x20; } // namespace openage::path diff --git a/libopenage/pathfinding/demo/demo_0.cpp b/libopenage/pathfinding/demo/demo_0.cpp index b2c5476127..0fca97c5f9 100644 --- a/libopenage/pathfinding/demo/demo_0.cpp +++ b/libopenage/pathfinding/demo/demo_0.cpp @@ -262,7 +262,7 @@ void RenderManager::show_vectors(const std::shared_ptr &field) for (size_t y = 0; y < field->get_size(); ++y) { for (size_t x = 0; x < field->get_size(); ++x) { auto cell = field->get_cell(x, y); - if (cell & FLOW_PATHABLE) { + if (cell & FLOW_PATHABLE_MASK) { Eigen::Affine3f arrow_model = Eigen::Affine3f::Identity(); arrow_model.translate(Eigen::Vector3f(y + 0.5, 0, -1.0f * x - 0.5)); auto dir = static_cast(cell & FLOW_DIR_MASK); diff --git a/libopenage/pathfinding/flow_field.cpp b/libopenage/pathfinding/flow_field.cpp index 36487d4613..4757a7cc95 100644 --- a/libopenage/pathfinding/flow_field.cpp +++ b/libopenage/pathfinding/flow_field.cpp @@ -12,13 +12,13 @@ namespace openage::path { FlowField::FlowField(size_t size) : size{size}, - cells(this->size * this->size, 0) { + cells(this->size * this->size, FLOW_INIT) { log::log(DBG << "Created flow field with size " << this->size << "x" << this->size); } FlowField::FlowField(const std::shared_ptr &integrate_field) : size{integrate_field->get_size()}, - cells(this->size * this->size, 0) { + cells(this->size * this->size, FLOW_INIT) { this->build(integrate_field); } @@ -117,7 +117,7 @@ void FlowField::build(const std::shared_ptr &integrate_field) } // Set the flow field cell to pathable. - flow_cells[idx] = flow_cells[idx] | FLOW_PATHABLE; + flow_cells[idx] = flow_cells[idx] | FLOW_PATHABLE_MASK; // Set the flow field cell to the direction of the smallest cost. flow_cells[idx] = flow_cells[idx] | static_cast(direction); @@ -131,7 +131,7 @@ const std::vector &FlowField::get_cells() const { void FlowField::reset() { for (auto &cell : this->cells) { - cell = 0; + cell = FLOW_INIT; } log::log(DBG << "Flow field has been reset"); diff --git a/libopenage/pathfinding/tests.cpp b/libopenage/pathfinding/tests.cpp index 6e060c4301..f7b9cae197 100644 --- a/libopenage/pathfinding/tests.cpp +++ b/libopenage/pathfinding/tests.cpp @@ -332,15 +332,15 @@ void flow_field() { // | SE | X | S | // | E | E | N | auto ff_expected = std::vector{ - FLOW_PATHABLE | static_cast(flow_dir_t::EAST), - FLOW_PATHABLE | static_cast(flow_dir_t::SOUTH_EAST), - FLOW_PATHABLE | static_cast(flow_dir_t::SOUTH), - FLOW_PATHABLE | static_cast(flow_dir_t::SOUTH_EAST), + FLOW_PATHABLE_MASK | static_cast(flow_dir_t::EAST), + FLOW_PATHABLE_MASK | static_cast(flow_dir_t::SOUTH_EAST), + FLOW_PATHABLE_MASK | static_cast(flow_dir_t::SOUTH), + FLOW_PATHABLE_MASK | static_cast(flow_dir_t::SOUTH_EAST), 0, - FLOW_PATHABLE | static_cast(flow_dir_t::SOUTH), - FLOW_PATHABLE | static_cast(flow_dir_t::EAST), - FLOW_PATHABLE | static_cast(flow_dir_t::EAST), - FLOW_PATHABLE | static_cast(flow_dir_t::NORTH), + FLOW_PATHABLE_MASK | static_cast(flow_dir_t::SOUTH), + FLOW_PATHABLE_MASK | static_cast(flow_dir_t::EAST), + FLOW_PATHABLE_MASK | static_cast(flow_dir_t::EAST), + FLOW_PATHABLE_MASK | static_cast(flow_dir_t::NORTH), }; // Test the different field types From 67d60675d1bf8d87a195d11976dcdb6e0971fed5 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 25 Feb 2024 23:24:14 +0100 Subject: [PATCH 036/112] path: Add flags to integration field cell type. --- libopenage/pathfinding/definitions.h | 23 +++++++++--- libopenage/pathfinding/demo/demo_0.cpp | 12 +++---- libopenage/pathfinding/flow_field.cpp | 20 +++++------ libopenage/pathfinding/integration_field.cpp | 38 ++++++++++---------- libopenage/pathfinding/integration_field.h | 20 +++++------ libopenage/pathfinding/integrator.cpp | 2 +- libopenage/pathfinding/tests.cpp | 6 ++-- libopenage/pathfinding/types.h | 27 ++++++++++++-- 8 files changed, 92 insertions(+), 56 deletions(-) diff --git a/libopenage/pathfinding/definitions.h b/libopenage/pathfinding/definitions.h index 5756ac8c61..561f7b58a0 100644 --- a/libopenage/pathfinding/definitions.h +++ b/libopenage/pathfinding/definitions.h @@ -35,12 +35,27 @@ constexpr cost_t COST_IMPASSABLE = 255; /** * Start value for goal cells. */ -const integrate_t INTEGRATE_START = 0; +const integrated_cost_t INTEGRATED_COST_START = 0; /** * Unreachable value for a cells in the integration grid. */ -constexpr integrate_t INTEGRATE_UNREACHABLE = std::numeric_limits::max(); +constexpr integrated_cost_t INTEGRATED_COST_UNREACHABLE = std::numeric_limits::max(); + +/** + * Line of sight flag in an integrated_flags_t value. + */ +constexpr integrated_flags_t INTEGRATE_LOS_MASK = 0x01; + +/** + * Wavefront blocked flag in an integrated_flags_t value. + */ +constexpr integrated_flags_t INTEGRATE_WAVEFRONT_BLOCKED_MASK = 0x02; + +/** + * Initial value for a cell in the integration grid. + */ +constexpr integrate_t INTEGRATE_INIT = {INTEGRATED_COST_UNREACHABLE, 0}; /** @@ -70,12 +85,12 @@ constexpr flow_t FLOW_INIT = 0; constexpr flow_t FLOW_DIR_MASK = 0x0F; /** - * Mask for the pathable flag in a flow_t value. + * Pathable flag in a flow_t value. */ constexpr flow_t FLOW_PATHABLE_MASK = 0x10; /** - * Mask for the line of sight flag in a flow_t value. + * Line of sight flag in a flow_t value. */ constexpr flow_t FLOW_LOS_MASK = 0x20; diff --git a/libopenage/pathfinding/demo/demo_0.cpp b/libopenage/pathfinding/demo/demo_0.cpp index 0fca97c5f9..a395fb5437 100644 --- a/libopenage/pathfinding/demo/demo_0.cpp +++ b/libopenage/pathfinding/demo/demo_0.cpp @@ -44,7 +44,7 @@ void path_demo_0(const util::Path &path) { log::log(INFO << "Created integration field"); // Set cell (7, 7) to be the target cell - integration_field->integrate(cost_field, 7, 7); + integration_field->integrate_cost(cost_field, 7, 7); log::log(INFO << "Calculated integration field for target cell (7, 7)"); // Create a flow field from the integration field @@ -80,7 +80,7 @@ void path_demo_0(const util::Path &path) { log::log(INFO << "Selected new target cell (" << grid_x << ", " << grid_y << ")"); // Recalculate the integration field and the flow field - integration_field->integrate(cost_field, grid_x, grid_y); + integration_field->integrate_cost(cost_field, grid_x, grid_y); log::log(INFO << "Calculated integration field for target cell (" << grid_x << ", " << grid_y << ")"); @@ -638,19 +638,19 @@ renderer::resources::MeshData RenderManager::get_integration_field_mesh(const st // for each vertex, compare the surrounding tiles std::vector surround{}; if (j - 1 >= 0 and i - 1 >= 0) { - auto cost = field->get_cell((i - 1) / resolution, (j - 1) / resolution); + auto cost = field->get_cell((i - 1) / resolution, (j - 1) / resolution).cost; surround.push_back(cost); } if (j < static_cast(field->get_size()) and i - 1 >= 0) { - auto cost = field->get_cell((i - 1) / resolution, j / resolution); + auto cost = field->get_cell((i - 1) / resolution, j / resolution).cost; surround.push_back(cost); } if (j < static_cast(field->get_size()) and i < static_cast(field->get_size())) { - auto cost = field->get_cell(i / resolution, j / resolution); + auto cost = field->get_cell(i / resolution, j / resolution).cost; surround.push_back(cost); } if (j - 1 >= 0 and i < static_cast(field->get_size())) { - auto cost = field->get_cell(i / resolution, (j - 1) / resolution); + auto cost = field->get_cell(i / resolution, (j - 1) / resolution).cost; surround.push_back(cost); } // use the cost of the most expensive surrounding tile diff --git a/libopenage/pathfinding/flow_field.cpp b/libopenage/pathfinding/flow_field.cpp index 4757a7cc95..afb050fd82 100644 --- a/libopenage/pathfinding/flow_field.cpp +++ b/libopenage/pathfinding/flow_field.cpp @@ -51,65 +51,65 @@ void FlowField::build(const std::shared_ptr &integrate_field) for (size_t x = 0; x < this->size; ++x) { size_t idx = y * this->size + x; - if (integrate_cells[idx] == INTEGRATE_UNREACHABLE) { + if (integrate_cells[idx].cost == INTEGRATED_COST_UNREACHABLE) { // Cell cannot be used as path continue; } // Find the neighbor with the smallest cost. flow_dir_t direction; - integrate_t smallest_cost = INTEGRATE_UNREACHABLE; + auto smallest_cost = INTEGRATED_COST_UNREACHABLE; if (y > 0) { - integrate_t cost = integrate_cells[idx - this->size]; + auto cost = integrate_cells[idx - this->size].cost; if (cost < smallest_cost) { smallest_cost = cost; direction = flow_dir_t::NORTH; } } if (x < this->size - 1 && y > 0) { - integrate_t cost = integrate_cells[idx - this->size + 1]; + auto cost = integrate_cells[idx - this->size + 1].cost; if (cost < smallest_cost) { smallest_cost = cost; direction = flow_dir_t::NORTH_EAST; } } if (x < this->size - 1) { - integrate_t cost = integrate_cells[idx + 1]; + auto cost = integrate_cells[idx + 1].cost; if (cost < smallest_cost) { smallest_cost = cost; direction = flow_dir_t::EAST; } } if (x < this->size - 1 && y < this->size - 1) { - integrate_t cost = integrate_cells[idx + this->size + 1]; + auto cost = integrate_cells[idx + this->size + 1].cost; if (cost < smallest_cost) { smallest_cost = cost; direction = flow_dir_t::SOUTH_EAST; } } if (y < this->size - 1) { - integrate_t cost = integrate_cells[idx + this->size]; + auto cost = integrate_cells[idx + this->size].cost; if (cost < smallest_cost) { smallest_cost = cost; direction = flow_dir_t::SOUTH; } } if (x > 0 && y < this->size - 1) { - integrate_t cost = integrate_cells[idx + this->size - 1]; + auto cost = integrate_cells[idx + this->size - 1].cost; if (cost < smallest_cost) { smallest_cost = cost; direction = flow_dir_t::SOUTH_WEST; } } if (x > 0) { - integrate_t cost = integrate_cells[idx - 1]; + auto cost = integrate_cells[idx - 1].cost; if (cost < smallest_cost) { smallest_cost = cost; direction = flow_dir_t::WEST; } } if (x > 0 && y > 0) { - integrate_t cost = integrate_cells[idx - this->size - 1]; + auto cost = integrate_cells[idx - this->size - 1].cost; if (cost < smallest_cost) { smallest_cost = cost; direction = flow_dir_t::NORTH_WEST; diff --git a/libopenage/pathfinding/integration_field.cpp b/libopenage/pathfinding/integration_field.cpp index aef6fc7594..f682db2320 100644 --- a/libopenage/pathfinding/integration_field.cpp +++ b/libopenage/pathfinding/integration_field.cpp @@ -13,7 +13,7 @@ namespace openage::path { IntegrationField::IntegrationField(size_t size) : size{size}, - cells(this->size * this->size, INTEGRATE_UNREACHABLE) { + cells(this->size * this->size, INTEGRATE_INIT) { log::log(DBG << "Created integration field with size " << this->size << "x" << this->size); } @@ -21,17 +21,17 @@ size_t IntegrationField::get_size() const { return this->size; } -integrate_t IntegrationField::get_cell(size_t x, size_t y) const { +const integrate_t &IntegrationField::get_cell(size_t x, size_t y) const { return this->cells.at(x + y * this->size); } -integrate_t IntegrationField::get_cell(size_t idx) const { +const integrate_t &IntegrationField::get_cell(size_t idx) const { return this->cells.at(idx); } -void IntegrationField::integrate(const std::shared_ptr &cost_field, - size_t target_x, - size_t target_y) { +void IntegrationField::integrate_cost(const std::shared_ptr &cost_field, + size_t target_x, + size_t target_y) { ENSURE(cost_field->get_size() == this->get_size(), "cost field size " << cost_field->get_size() << "x" << cost_field->get_size() @@ -57,7 +57,7 @@ void IntegrationField::integrate(const std::shared_ptr &cost_field, neighbors.reserve(4); // Move outwards from the target cell, updating the integration field - this->cells[target_idx] = INTEGRATE_START; + this->cells[target_idx].cost = INTEGRATED_COST_START; open_list.push_back(target_idx); while (!open_list.empty()) { auto idx = open_list.front(); @@ -67,7 +67,7 @@ void IntegrationField::integrate(const std::shared_ptr &cost_field, auto x = idx % this->size; auto y = idx / this->size; - auto integrated_current = this->cells[idx]; + auto integrated_current = this->cells.at(idx).cost; // Get the neighbors of the current cell if (y > 0) { @@ -106,37 +106,35 @@ const std::vector &IntegrationField::get_cells() const { void IntegrationField::reset() { for (auto &cell : this->cells) { - cell = INTEGRATE_UNREACHABLE; + cell = INTEGRATE_INIT; } log::log(DBG << "Integration field has been reset"); } -integrate_t IntegrationField::update_neighbor(size_t idx, - cost_t cell_cost, - integrate_t integrated_cost, - std::deque &open_list, - std::unordered_set &in_list) { +void IntegrationField::update_neighbor(size_t idx, + cost_t cell_cost, + integrated_cost_t integrated_cost, + std::deque &open_list, + std::unordered_set &in_list) { ENSURE(cell_cost > COST_INIT, "cost field cell value must be non-zero"); // Check if the cell is impassable // then we don't need to update the integration field if (cell_cost == COST_IMPASSABLE) { - return INTEGRATE_UNREACHABLE; + return; } - auto integrated = integrated_cost + cell_cost; - if (integrated < this->cells.at(idx)) { + auto cost = integrated_cost + cell_cost; + if (cost < this->cells.at(idx).cost) { // If the new integration value is smaller than the current one, // update the cell and add it to the open list - this->cells[idx] = integrated; + this->cells[idx].cost = cost; if (not in_list.contains(idx)) { in_list.insert(idx); open_list.push_back(idx); } } - - return integrated; } diff --git a/libopenage/pathfinding/integration_field.h b/libopenage/pathfinding/integration_field.h index 1860c6cfad..81cb224111 100644 --- a/libopenage/pathfinding/integration_field.h +++ b/libopenage/pathfinding/integration_field.h @@ -40,7 +40,7 @@ class IntegrationField { * @param y Y coordinate. * @return Integration value at the specified position. */ - integrate_t get_cell(size_t x, size_t y) const; + const integrate_t &get_cell(size_t x, size_t y) const; /** * Get the integration value at a specified position. @@ -48,7 +48,7 @@ class IntegrationField { * @param idx Index of the cell. * @return Integration value at the specified position. */ - integrate_t get_cell(size_t idx) const; + const integrate_t &get_cell(size_t idx) const; /** * Calculate the integration field for a target cell. @@ -57,9 +57,9 @@ class IntegrationField { * @param target_x X coordinate of the target cell. * @param target_y Y coordinate of the target cell. */ - void integrate(const std::shared_ptr &cost_field, - size_t target_x, - size_t target_y); + void integrate_cost(const std::shared_ptr &cost_field, + size_t target_x, + size_t target_y); /** * Get the integration field values. @@ -83,11 +83,11 @@ class IntegrationField { * * @return New integration value of the cell. */ - integrate_t update_neighbor(size_t idx, - cost_t cell_cost, - integrate_t integrated_cost, - std::deque &open_list, - std::unordered_set &in_list); + void update_neighbor(size_t idx, + cost_t cell_cost, + integrated_cost_t integrated_cost, + std::deque &open_list, + std::unordered_set &in_list); /** * Side length of the field. diff --git a/libopenage/pathfinding/integrator.cpp b/libopenage/pathfinding/integrator.cpp index 19cd0c408f..3f5d2af252 100644 --- a/libopenage/pathfinding/integrator.cpp +++ b/libopenage/pathfinding/integrator.cpp @@ -17,7 +17,7 @@ std::shared_ptr Integrator::build(size_t target_x, size_t target_y) { auto flow_field = std::make_shared(this->cost_field->get_size()); auto integrate_field = std::make_shared(this->cost_field->get_size()); - integrate_field->integrate(this->cost_field, target_x, target_y); + integrate_field->integrate_cost(this->cost_field, target_x, target_y); flow_field->build(integrate_field); return flow_field; diff --git a/libopenage/pathfinding/tests.cpp b/libopenage/pathfinding/tests.cpp index f7b9cae197..7b15a4406a 100644 --- a/libopenage/pathfinding/tests.cpp +++ b/libopenage/pathfinding/tests.cpp @@ -346,14 +346,14 @@ void flow_field() { // Test the different field types { auto integration_field = std::make_shared(3); - integration_field->integrate(cost_field, 2, 2); + integration_field->integrate_cost(cost_field, 2, 2); auto int_cells = integration_field->get_cells(); // The integration field should look like: // | 4 | 3 | 2 | // | 3 | X | 1 | // | 2 | 1 | 0 | - auto int_expected = std::vector{ + auto int_expected = std::vector{ 4, 3, 2, @@ -367,7 +367,7 @@ void flow_field() { // Compare the integration field cells with the expected values for (size_t i = 0; i < int_cells.size(); i++) { - TESTEQUALS(int_cells[i], int_expected[i]); + TESTEQUALS(int_cells[i].cost, int_expected[i]); } // Build the flow field diff --git a/libopenage/pathfinding/types.h b/libopenage/pathfinding/types.h index d68f8b39ca..55e8bb9da5 100644 --- a/libopenage/pathfinding/types.h +++ b/libopenage/pathfinding/types.h @@ -17,9 +17,32 @@ namespace openage::path { using cost_t = uint8_t; /** - * Total integrated cost in the integration field. + * Integrated cost in the integration field. */ -using integrate_t = uint16_t; +using integrated_cost_t = uint16_t; + +/** + * Integrated field cell flags. + */ +using integrated_flags_t = uint8_t; + +/** + * Integration field cell value. + */ +struct integrate_t { + /** + * Total integrated cost. + */ + integrated_cost_t cost; + + /** + * Flags. + * + * Bit 6: Wave front blocked flag. + * Bit 7: Line of sight flag. + */ + integrated_flags_t flags; +}; /** * Flow field cell value. From 824fcca0a2d49c45089dc052a308044f63fadf56 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 25 Feb 2024 23:28:00 +0100 Subject: [PATCH 037/112] path: Check for line of sight flag when building flow field. --- libopenage/pathfinding/flow_field.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/libopenage/pathfinding/flow_field.cpp b/libopenage/pathfinding/flow_field.cpp index afb050fd82..e728199fd9 100644 --- a/libopenage/pathfinding/flow_field.cpp +++ b/libopenage/pathfinding/flow_field.cpp @@ -56,6 +56,12 @@ void FlowField::build(const std::shared_ptr &integrate_field) continue; } + if (integrate_cells[idx].flags & INTEGRATE_LOS_MASK) { + // Cell is in line of sight + this->cells[idx] = this->cells[idx] | FLOW_LOS_MASK; + // continue; + } + // Find the neighbor with the smallest cost. flow_dir_t direction; auto smallest_cost = INTEGRATED_COST_UNREACHABLE; From 35533ce7948d3784905590b9c972540a81e75a87 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sat, 2 Mar 2024 23:01:51 +0100 Subject: [PATCH 038/112] path: LOS pass in integration field. --- libopenage/pathfinding/integration_field.cpp | 68 ++++++++++++++++++++ libopenage/pathfinding/integration_field.h | 11 ++++ 2 files changed, 79 insertions(+) diff --git a/libopenage/pathfinding/integration_field.cpp b/libopenage/pathfinding/integration_field.cpp index f682db2320..fc4b0557cf 100644 --- a/libopenage/pathfinding/integration_field.cpp +++ b/libopenage/pathfinding/integration_field.cpp @@ -29,6 +29,74 @@ const integrate_t &IntegrationField::get_cell(size_t idx) const { return this->cells.at(idx); } +void IntegrationField::integrate_los(const std::shared_ptr &cost_field, + size_t target_x, + size_t target_y) { + ENSURE(cost_field->get_size() == this->get_size(), + "cost field size " + << cost_field->get_size() << "x" << cost_field->get_size() + << " does not match integration field size " + << this->get_size() << "x" << this->get_size()); + + // Reset the integration field + this->reset(); + + + // Target cell index + auto target_idx = target_x + target_y * this->size; + + // Lookup table for cells that have been found + std::unordered_set found; + found.reserve(this->size * this->size); + + // Cells that still have to be visited + std::deque open_list; + + // Move outwards from the target cell, updating the integration field + open_list.push_back(target_idx); + while (!open_list.empty()) { + auto idx = open_list.front(); + open_list.pop_front(); + + if (found.contains(idx)) { + // Skip cells that have already been found + continue; + } + + // Get the x and y coordinates of the current cell + auto x = idx % this->size; + auto y = idx / this->size; + + // Get the cost of the current cell + auto cell_cost = cost_field->get_cost(idx); + + if (cell_cost > COST_MIN) { + // TODO: stop the LOS search and check for LOS corners + continue; + } + + // Set the LOS flag of the current cell + this->cells[idx].flags |= INTEGRATE_LOS_MASK; + + // Search the neighbors of the current cell + if (y > 0) { + open_list.push_back(idx - this->size); + } + if (x > 0) { + open_list.push_back(idx - 1); + } + if (y < this->size - 1) { + open_list.push_back(idx + this->size); + } + if (x < this->size - 1) { + open_list.push_back(idx + 1); + } + + // Add the current cell to the found cells + found.insert(idx); + } +} + void IntegrationField::integrate_cost(const std::shared_ptr &cost_field, size_t target_x, size_t target_y) { diff --git a/libopenage/pathfinding/integration_field.h b/libopenage/pathfinding/integration_field.h index 81cb224111..c65e038cfa 100644 --- a/libopenage/pathfinding/integration_field.h +++ b/libopenage/pathfinding/integration_field.h @@ -50,6 +50,17 @@ class IntegrationField { */ const integrate_t &get_cell(size_t idx) const; + /** + * Calculate the line-of-sight cells for a target cell. + * + * @param cost_field Cost field to integrate. + * @param target_x X coordinate of the target cell. + * @param target_y Y coordinate of the target cell. + */ + void integrate_los(const std::shared_ptr &cost_field, + size_t target_x, + size_t target_y); + /** * Calculate the integration field for a target cell. * From a42fc33372d2b99549e0ab25035436e99e7eee09 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sat, 2 Mar 2024 23:06:57 +0100 Subject: [PATCH 039/112] path: Manual resetting of integration/flow field. --- libopenage/pathfinding/demo/demo_0.cpp | 4 +++- libopenage/pathfinding/flow_field.cpp | 3 --- libopenage/pathfinding/flow_field.h | 2 +- libopenage/pathfinding/integration_field.cpp | 7 ------- libopenage/pathfinding/integration_field.h | 2 +- 5 files changed, 5 insertions(+), 13 deletions(-) diff --git a/libopenage/pathfinding/demo/demo_0.cpp b/libopenage/pathfinding/demo/demo_0.cpp index a395fb5437..828d9acece 100644 --- a/libopenage/pathfinding/demo/demo_0.cpp +++ b/libopenage/pathfinding/demo/demo_0.cpp @@ -80,10 +80,12 @@ void path_demo_0(const util::Path &path) { log::log(INFO << "Selected new target cell (" << grid_x << ", " << grid_y << ")"); // Recalculate the integration field and the flow field + integration_field->reset(); integration_field->integrate_cost(cost_field, grid_x, grid_y); log::log(INFO << "Calculated integration field for target cell (" << grid_x << ", " << grid_y << ")"); + flow_field->reset(); flow_field->build(integration_field); log::log(INFO << "Built flow field from integration field"); @@ -302,7 +304,7 @@ std::pair RenderManager::select_tile(double x, double y) { auto grid_plane_point = Eigen::Vector3f{0, 0, 0}; auto camera_direction = renderer::camera::cam_direction; auto camera_position = camera->get_input_pos( - coord::input{x, y}); + coord::input(x, y)); Eigen::Vector3f intersect = camera_position + camera_direction * (grid_plane_point - camera_position).dot(grid_plane_normal) / camera_direction.dot(grid_plane_normal); auto grid_x = static_cast(-1 * intersect[2]); diff --git a/libopenage/pathfinding/flow_field.cpp b/libopenage/pathfinding/flow_field.cpp index e728199fd9..baa3c494e7 100644 --- a/libopenage/pathfinding/flow_field.cpp +++ b/libopenage/pathfinding/flow_field.cpp @@ -41,9 +41,6 @@ void FlowField::build(const std::shared_ptr &integrate_field) << " does not match flow field size " << this->get_size() << "x" << this->get_size()); - // Reset the flow field. - this->reset(); - auto &integrate_cells = integrate_field->get_cells(); auto &flow_cells = this->cells; diff --git a/libopenage/pathfinding/flow_field.h b/libopenage/pathfinding/flow_field.h index 2fefd251ba..5d8ab25580 100644 --- a/libopenage/pathfinding/flow_field.h +++ b/libopenage/pathfinding/flow_field.h @@ -70,12 +70,12 @@ class FlowField { */ const std::vector &get_cells() const; -private: /** * Reset the flow field values for rebuilding the field. */ void reset(); +private: /** * Side length of the field. */ diff --git a/libopenage/pathfinding/integration_field.cpp b/libopenage/pathfinding/integration_field.cpp index fc4b0557cf..2db57a4367 100644 --- a/libopenage/pathfinding/integration_field.cpp +++ b/libopenage/pathfinding/integration_field.cpp @@ -38,10 +38,6 @@ void IntegrationField::integrate_los(const std::shared_ptr &cost_fiel << " does not match integration field size " << this->get_size() << "x" << this->get_size()); - // Reset the integration field - this->reset(); - - // Target cell index auto target_idx = target_x + target_y * this->size; @@ -106,9 +102,6 @@ void IntegrationField::integrate_cost(const std::shared_ptr &cost_fie << " does not match integration field size " << this->get_size() << "x" << this->get_size()); - // Reset the integration field - this->reset(); - // Target cell index auto target_idx = target_x + target_y * this->size; diff --git a/libopenage/pathfinding/integration_field.h b/libopenage/pathfinding/integration_field.h index c65e038cfa..707fc341b3 100644 --- a/libopenage/pathfinding/integration_field.h +++ b/libopenage/pathfinding/integration_field.h @@ -79,12 +79,12 @@ class IntegrationField { */ const std::vector &get_cells() const; -private: /** * Reset the integration field for a new integration. */ void reset(); +private: /** * Update a neigbor cell during the integration process. * From ce0210bc4d28f37b554f21b80f6bcc31f7712587 Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 4 Mar 2024 02:42:24 +0100 Subject: [PATCH 040/112] path: Handle LOS corners. --- .../pathfinding/demo_0_flow_field.frag.glsl | 15 +- libopenage/pathfinding/definitions.h | 5 + libopenage/pathfinding/demo/demo_0.cpp | 4 +- libopenage/pathfinding/flow_field.cpp | 7 +- libopenage/pathfinding/integration_field.cpp | 245 +++++++++++++++++- libopenage/pathfinding/integration_field.h | 34 ++- libopenage/pathfinding/integrator.cpp | 1 + libopenage/pathfinding/types.h | 6 +- 8 files changed, 302 insertions(+), 15 deletions(-) diff --git a/assets/test/shaders/pathfinding/demo_0_flow_field.frag.glsl b/assets/test/shaders/pathfinding/demo_0_flow_field.frag.glsl index 51f4a66804..b5ff4042a3 100644 --- a/assets/test/shaders/pathfinding/demo_0_flow_field.frag.glsl +++ b/assets/test/shaders/pathfinding/demo_0_flow_field.frag.glsl @@ -6,9 +6,9 @@ out vec4 outcol; void main() { int cost = int(v_cost); - if (!bool(cost & 0x10)) { - // not pathable - outcol = vec4(0.0, 0.0, 0.0, 1.0); + if (bool(cost & 0x40)) { + // wavefront blocked + outcol = vec4(0.9, 0.9, 0.9, 1.0); return; } @@ -18,5 +18,12 @@ void main() { return; } - outcol = vec4(0.7, 0.7, 0.7, 1.0); + if (bool(cost & 0x10)) { + // pathable + outcol = vec4(0.7, 0.7, 0.7, 1.0); + return; + } + + // not pathable + outcol = vec4(0.0, 0.0, 0.0, 1.0); } diff --git a/libopenage/pathfinding/definitions.h b/libopenage/pathfinding/definitions.h index 561f7b58a0..3d8584616b 100644 --- a/libopenage/pathfinding/definitions.h +++ b/libopenage/pathfinding/definitions.h @@ -94,4 +94,9 @@ constexpr flow_t FLOW_PATHABLE_MASK = 0x10; */ constexpr flow_t FLOW_LOS_MASK = 0x20; +/** + * Wavefront blocked flag in a flow_t value. + */ +constexpr flow_t FLOW_WAVEFRONT_BLOCKED_MASK = 0x40; + } // namespace openage::path diff --git a/libopenage/pathfinding/demo/demo_0.cpp b/libopenage/pathfinding/demo/demo_0.cpp index 828d9acece..66aec6417e 100644 --- a/libopenage/pathfinding/demo/demo_0.cpp +++ b/libopenage/pathfinding/demo/demo_0.cpp @@ -44,6 +44,7 @@ void path_demo_0(const util::Path &path) { log::log(INFO << "Created integration field"); // Set cell (7, 7) to be the target cell + integration_field->integrate_los(cost_field, 7, 7); integration_field->integrate_cost(cost_field, 7, 7); log::log(INFO << "Calculated integration field for target cell (7, 7)"); @@ -81,6 +82,7 @@ void path_demo_0(const util::Path &path) { // Recalculate the integration field and the flow field integration_field->reset(); + integration_field->integrate_los(cost_field, grid_x, grid_y); integration_field->integrate_cost(cost_field, grid_x, grid_y); log::log(INFO << "Calculated integration field for target cell (" << grid_x << ", " << grid_y << ")"); @@ -237,7 +239,7 @@ void RenderManager::show_flow_field(const std::shared_ptr &fiel this->camera->get_view_matrix(), "proj", this->camera->get_projection_matrix()); - auto mesh = get_flow_field_mesh(field); + auto mesh = get_flow_field_mesh(field, 4); auto geometry = this->renderer->add_mesh_geometry(mesh); renderer::Renderable renderable{ unifs, diff --git a/libopenage/pathfinding/flow_field.cpp b/libopenage/pathfinding/flow_field.cpp index baa3c494e7..64e7d82d23 100644 --- a/libopenage/pathfinding/flow_field.cpp +++ b/libopenage/pathfinding/flow_field.cpp @@ -56,7 +56,12 @@ void FlowField::build(const std::shared_ptr &integrate_field) if (integrate_cells[idx].flags & INTEGRATE_LOS_MASK) { // Cell is in line of sight this->cells[idx] = this->cells[idx] | FLOW_LOS_MASK; - // continue; + continue; + } + + if (integrate_cells[idx].flags & INTEGRATE_WAVEFRONT_BLOCKED_MASK) { + // Cell is blocked by a line-of-sight corner + this->cells[idx] = this->cells[idx] | FLOW_WAVEFRONT_BLOCKED_MASK; } // Find the neighbor with the smallest cost. diff --git a/libopenage/pathfinding/integration_field.cpp b/libopenage/pathfinding/integration_field.cpp index 2db57a4367..5e6459f873 100644 --- a/libopenage/pathfinding/integration_field.cpp +++ b/libopenage/pathfinding/integration_field.cpp @@ -2,6 +2,8 @@ #include "integration_field.h" +#include + #include "error/error.h" #include "log/log.h" @@ -58,6 +60,13 @@ void IntegrationField::integrate_los(const std::shared_ptr &cost_fiel // Skip cells that have already been found continue; } + else if (this->cells[idx].flags & INTEGRATE_WAVEFRONT_BLOCKED_MASK) { + // Skip cells that are blocked by a LOS corner + continue; + } + + // Add the current cell to the found cells + found.insert(idx); // Get the x and y coordinates of the current cell auto x = idx % this->size; @@ -67,7 +76,16 @@ void IntegrationField::integrate_los(const std::shared_ptr &cost_fiel auto cell_cost = cost_field->get_cost(idx); if (cell_cost > COST_MIN) { - // TODO: stop the LOS search and check for LOS corners + // check each neighbor for a corner + auto corners = this->get_los_corners(cost_field, target_x, target_y, x, y); + + for (auto &corner : corners) { + auto blocked_cells = this->bresenhams_line(target_x, target_y, corner.first, corner.second); + for (auto &blocked_idx : blocked_cells) { + this->cells[blocked_idx].flags |= INTEGRATE_WAVEFRONT_BLOCKED_MASK; + } + } + continue; } @@ -87,9 +105,6 @@ void IntegrationField::integrate_los(const std::shared_ptr &cost_fiel if (x < this->size - 1) { open_list.push_back(idx + 1); } - - // Add the current cell to the found cells - found.insert(idx); } } @@ -198,5 +213,227 @@ void IntegrationField::update_neighbor(size_t idx, } } +std::vector> IntegrationField::get_los_corners(const std::shared_ptr &cost_field, + size_t target_x, + size_t target_y, + size_t blocker_x, + size_t blocker_y) { + std::vector> corners; + + // Get the cost of the blocking cell's neighbors + + // Set all costs to MAX at the beginning + auto top_cost = COST_MAX; + auto left_cost = COST_MAX; + auto bottom_cost = COST_MAX; + auto right_cost = COST_MAX; + + std::pair top_left{blocker_x, blocker_y}; + std::pair top_right{blocker_x + 1, blocker_y}; + std::pair bottom_left{blocker_x, blocker_y + 1}; + std::pair bottom_right{blocker_x + 1, blocker_y + 1}; + + // Get neighbor costs (if they exist) + if (blocker_y > 0) { + top_cost = cost_field->get_cost(blocker_x, blocker_y - 1); + } + if (blocker_x > 0) { + left_cost = cost_field->get_cost(blocker_x - 1, blocker_y); + } + if (blocker_y < this->size - 1) { + bottom_cost = cost_field->get_cost(blocker_x, blocker_y + 1); + } + if (blocker_x < this->size - 1) { + right_cost = cost_field->get_cost(blocker_x + 1, blocker_y); + } + + // Check which corners are blocking LOS + // TODO: Currently super complicated and could likely be optimized + if (blocker_x == target_x) { + // blocking cell is parallel to target on y-axis + if (blocker_y < target_y) { + if (left_cost == COST_MIN) { + // top + corners.push_back(bottom_left); + } + if (right_cost == COST_MIN) { + // top + corners.push_back(bottom_right); + } + } + else { + if (left_cost == COST_MIN) { + // bottom + corners.push_back(top_left); + } + if (right_cost == COST_MIN) { + // bottom + corners.push_back(top_right); + } + } + } + else if (blocker_y == target_y) { + // blocking cell is parallel to target on x-axis + if (blocker_x < target_x) { + if (top_cost == COST_MIN) { + // right + corners.push_back(top_right); + } + if (bottom_cost == COST_MIN) { + // right + corners.push_back(bottom_right); + } + } + else { + if (top_cost == COST_MIN) { + // left + corners.push_back(top_left); + } + if (bottom_cost == COST_MIN) { + // left + corners.push_back(bottom_left); + } + } + } + else { + // blocking cell is diagonal to target on + if (blocker_x < target_x) { + if (blocker_y < target_y) { + // top and right + if (top_cost == COST_MIN and right_cost == COST_MIN) { + // right + corners.push_back(top_right); + } + if (left_cost == COST_MIN and bottom_cost == COST_MIN) { + // bottom + corners.push_back(bottom_left); + } + } + else { + // bottom and right + if (bottom_cost == COST_MIN and right_cost == COST_MIN) { + // right + corners.push_back(bottom_right); + } + if (left_cost == COST_MIN and top_cost == COST_MIN) { + // top + corners.push_back(top_left); + } + } + } + else { + if (blocker_y < target_y) { + // top and left + if (top_cost == COST_MIN and left_cost == COST_MIN) { + // left + corners.push_back(top_left); + } + if (right_cost == COST_MIN and bottom_cost == COST_MIN) { + // bottom + corners.push_back(bottom_right); + } + } + else { + // bottom and left + if (bottom_cost == COST_MIN and left_cost == COST_MIN) { + // left + corners.push_back(bottom_left); + } + if (right_cost == COST_MIN and top_cost == COST_MIN) { + // top + corners.push_back(top_right); + } + } + } + } + + return corners; +} + +std::vector IntegrationField::bresenhams_line(int target_x, + int target_y, + int corner_x, + int corner_y) { + std::vector cells; + + auto x = corner_x; + auto y = corner_y; + double tx = target_x + 0.5; + double ty = target_y + 0.5; + double dx = std::abs(tx - corner_x); + double dy = std::abs(ty - corner_y); + auto m = dy / dx; + + auto error = 0.5; + + // Check which direction the line is going + if (corner_x < tx) { + if (corner_y < ty) { + // left and up + y -= 1; + while (x > 0 and y > 0) { + if (error > 1.0) { + y -= 1; + error -= 1.0; + } + else { + x -= 1; + error += m; + } + cells.push_back(x + y * this->size); + } + } + else { + // left and down + while (x > 0 and y < this->size - 1) { + if (error > 1.0) { + y += 1; + error -= 1.0; + } + else { + x -= 1; + error += m; + } + cells.push_back(x + y * this->size); + } + } + } + else { + if (corner_y < ty) { + // right and up + x -= 1; + y -= 1; + while (x < this->size - 1 and y > 0) { + if (error > 1.0) { + y -= 1; + error -= 1.0; + } + else { + x += 1; + error += m; + } + cells.push_back(x + y * this->size); + } + } + else { + // right and down + x -= 1; + while (x < this->size - 1 and y < this->size - 1) { + if (error > 1.0) { + y += 1; + error -= 1.0; + } + else { + x += 1; + error += m; + } + cells.push_back(x + y * this->size); + } + } + } + + return cells; +} + } // namespace openage::path diff --git a/libopenage/pathfinding/integration_field.h b/libopenage/pathfinding/integration_field.h index 707fc341b3..263a948fc1 100644 --- a/libopenage/pathfinding/integration_field.h +++ b/libopenage/pathfinding/integration_field.h @@ -51,7 +51,7 @@ class IntegrationField { const integrate_t &get_cell(size_t idx) const; /** - * Calculate the line-of-sight cells for a target cell. + * Calculate the line-of-sight integration flags for a target cell. * * @param cost_field Cost field to integrate. * @param target_x X coordinate of the target cell. @@ -62,7 +62,7 @@ class IntegrationField { size_t target_y); /** - * Calculate the integration field for a target cell. + * Calculate the cost integration field for a target cell. * * @param cost_field Cost field to integrate. * @param target_x X coordinate of the target cell. @@ -86,7 +86,7 @@ class IntegrationField { private: /** - * Update a neigbor cell during the integration process. + * Update a neigbor cell during the cost integration process. * * @param idx Index of the neighbor cell that is updated. * @param cell_cost Cost of the neighbor cell from the cost field. @@ -100,6 +100,34 @@ class IntegrationField { std::deque &open_list, std::unordered_set &in_list); + /** + * Get the LOS corners around a cell. + * + * @param cost_field Cost field to integrate. + * @param target_x X coordinate of the target. + * @param target_y Y coordinate of the target. + * @param blocker_x X coordinate of the cell blocking LOS. + * @param blocker_y Y coordinate of the cell blocking LOS. + */ + std::vector> get_los_corners(const std::shared_ptr &cost_field, + size_t target_x, + size_t target_y, + size_t blocker_x, + size_t blocker_y); + + /** + * Get the cells in a bresenham's line between the corner cell and the field edge. + * + * @param target_x X coordinate of the target. + * @param target_y Y coordinate of the target. + * @param corner_x X coordinate edge of the LOS corner. + * @param corner_y Y coordinate edge of the LOS corner. + */ + std::vector bresenhams_line(int target_x, + int target_y, + int corner_x, + int corner_y); + /** * Side length of the field. */ diff --git a/libopenage/pathfinding/integrator.cpp b/libopenage/pathfinding/integrator.cpp index 3f5d2af252..cbff7fd7cb 100644 --- a/libopenage/pathfinding/integrator.cpp +++ b/libopenage/pathfinding/integrator.cpp @@ -17,6 +17,7 @@ std::shared_ptr Integrator::build(size_t target_x, size_t target_y) { auto flow_field = std::make_shared(this->cost_field->get_size()); auto integrate_field = std::make_shared(this->cost_field->get_size()); + integrate_field->integrate_los(this->cost_field, target_x, target_y); integrate_field->integrate_cost(this->cost_field, target_x, target_y); flow_field->build(integrate_field); diff --git a/libopenage/pathfinding/types.h b/libopenage/pathfinding/types.h index 55e8bb9da5..8280dfcdf8 100644 --- a/libopenage/pathfinding/types.h +++ b/libopenage/pathfinding/types.h @@ -47,8 +47,10 @@ struct integrate_t { /** * Flow field cell value. * - * Bit 2: Line of sight flag. - * Bit 3: Pathable flag. + * Bit 0: Line of sight flag. + * Bit 1: Pathable flag. + * Bit 2: Wavefront blocked flag. + * Bit 3: Unused. * Bits 4-7: flow direction. */ using flow_t = uint8_t; From 1ee2baa5f6afab6583a4cdd4a3b25fdc5ec0ce95 Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 4 Mar 2024 02:59:20 +0100 Subject: [PATCH 041/112] path: Add another impassible cell to demo. --- libopenage/pathfinding/demo/demo_0.cpp | 3 ++- libopenage/pathfinding/integration_field.cpp | 10 +++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/libopenage/pathfinding/demo/demo_0.cpp b/libopenage/pathfinding/demo/demo_0.cpp index 66aec6417e..b8462a7f27 100644 --- a/libopenage/pathfinding/demo/demo_0.cpp +++ b/libopenage/pathfinding/demo/demo_0.cpp @@ -29,7 +29,7 @@ void path_demo_0(const util::Path &path) { // Cost field with some obstacles auto cost_field = std::make_shared(field_length); - cost_field->set_cost(0, 0, 255); + cost_field->set_cost(0, 0, COST_IMPASSABLE); cost_field->set_cost(1, 0, 254); cost_field->set_cost(4, 3, 128); cost_field->set_cost(5, 3, 128); @@ -37,6 +37,7 @@ void path_demo_0(const util::Path &path) { cost_field->set_cost(4, 4, 128); cost_field->set_cost(5, 4, 128); cost_field->set_cost(6, 4, 128); + cost_field->set_cost(1, 7, COST_IMPASSABLE); log::log(INFO << "Created cost field"); // Create an integration field from the cost field diff --git a/libopenage/pathfinding/integration_field.cpp b/libopenage/pathfinding/integration_field.cpp index 5e6459f873..587806de44 100644 --- a/libopenage/pathfinding/integration_field.cpp +++ b/libopenage/pathfinding/integration_field.cpp @@ -222,11 +222,11 @@ std::vector> IntegrationField::get_los_corners(const s // Get the cost of the blocking cell's neighbors - // Set all costs to MAX at the beginning - auto top_cost = COST_MAX; - auto left_cost = COST_MAX; - auto bottom_cost = COST_MAX; - auto right_cost = COST_MAX; + // Set all costs to IMPASSABLE at the beginning + auto top_cost = COST_IMPASSABLE; + auto left_cost = COST_IMPASSABLE; + auto bottom_cost = COST_IMPASSABLE; + auto right_cost = COST_IMPASSABLE; std::pair top_left{blocker_x, blocker_y}; std::pair top_right{blocker_x + 1, blocker_y}; From a36931f7480124cc0ebea210b80bdfd7f35406ed Mon Sep 17 00:00:00 2001 From: heinezen Date: Fri, 8 Mar 2024 00:13:48 +0100 Subject: [PATCH 042/112] path: Integrate cost withexisting wavefront. --- libopenage/pathfinding/integration_field.cpp | 29 ++++++++++++++++---- libopenage/pathfinding/integration_field.h | 23 +++++++++++++--- 2 files changed, 42 insertions(+), 10 deletions(-) diff --git a/libopenage/pathfinding/integration_field.cpp b/libopenage/pathfinding/integration_field.cpp index 587806de44..d871192c36 100644 --- a/libopenage/pathfinding/integration_field.cpp +++ b/libopenage/pathfinding/integration_field.cpp @@ -31,15 +31,18 @@ const integrate_t &IntegrationField::get_cell(size_t idx) const { return this->cells.at(idx); } -void IntegrationField::integrate_los(const std::shared_ptr &cost_field, - size_t target_x, - size_t target_y) { +std::vector IntegrationField::integrate_los(const std::shared_ptr &cost_field, + size_t target_x, + size_t target_y) { ENSURE(cost_field->get_size() == this->get_size(), "cost field size " << cost_field->get_size() << "x" << cost_field->get_size() << " does not match integration field size " << this->get_size() << "x" << this->get_size()); + // Store the wavefront cells + std::vector wavefront; + // Target cell index auto target_idx = target_x + target_y * this->size; @@ -84,6 +87,7 @@ void IntegrationField::integrate_los(const std::shared_ptr &cost_fiel for (auto &blocked_idx : blocked_cells) { this->cells[blocked_idx].flags |= INTEGRATE_WAVEFRONT_BLOCKED_MASK; } + wavefront.insert(wavefront.end(), blocked_cells.begin(), blocked_cells.end()); } continue; @@ -106,6 +110,8 @@ void IntegrationField::integrate_los(const std::shared_ptr &cost_fiel open_list.push_back(idx + 1); } } + + return wavefront; } void IntegrationField::integrate_cost(const std::shared_ptr &cost_field, @@ -120,6 +126,13 @@ void IntegrationField::integrate_cost(const std::shared_ptr &cost_fie // Target cell index auto target_idx = target_x + target_y * this->size; + // Move outwards from the target cell, updating the integration field + this->cells[target_idx].cost = INTEGRATED_COST_START; + this->integrate_cost(cost_field, {target_idx}); +} + +void IntegrationField::integrate_cost(const std::shared_ptr &cost_field, + std::vector &&start_cells) { // Lookup table for cells that are in the open list std::unordered_set in_list; in_list.reserve(this->size * this->size); @@ -132,9 +145,8 @@ void IntegrationField::integrate_cost(const std::shared_ptr &cost_fie std::vector neighbors; neighbors.reserve(4); - // Move outwards from the target cell, updating the integration field - this->cells[target_idx].cost = INTEGRATED_COST_START; - open_list.push_back(target_idx); + // Move outwards from the wavefront, updating the integration field + open_list.insert(open_list.end(), start_cells.begin(), start_cells.end()); while (!open_list.empty()) { auto idx = open_list.front(); open_list.pop_front(); @@ -200,6 +212,11 @@ void IntegrationField::update_neighbor(size_t idx, return; } + // if (this->cells.at(idx).flags & INTEGRATE_LOS_MASK) { + // // If the cell is part of the LOS, we don't need to update it + // return; + // } + auto cost = integrated_cost + cell_cost; if (cost < this->cells.at(idx).cost) { // If the new integration value is smaller than the current one, diff --git a/libopenage/pathfinding/integration_field.h b/libopenage/pathfinding/integration_field.h index 263a948fc1..43621f1959 100644 --- a/libopenage/pathfinding/integration_field.h +++ b/libopenage/pathfinding/integration_field.h @@ -53,25 +53,40 @@ class IntegrationField { /** * Calculate the line-of-sight integration flags for a target cell. * + * Returns a list of cells that are flagged as "wavefront blocked". These cells + * can be used as a starting point for the cost integration. + * * @param cost_field Cost field to integrate. * @param target_x X coordinate of the target cell. * @param target_y Y coordinate of the target cell. + * + * @return Cells flagged as "wavefront blocked". */ - void integrate_los(const std::shared_ptr &cost_field, - size_t target_x, - size_t target_y); + std::vector integrate_los(const std::shared_ptr &cost_field, + size_t target_x, + size_t target_y); /** - * Calculate the cost integration field for a target cell. + * Calculate the cost integration field starting from a target cell. * * @param cost_field Cost field to integrate. * @param target_x X coordinate of the target cell. * @param target_y Y coordinate of the target cell. + * @param start_cells Cells flagged as "wavefront blocked" from a LOS pass. */ void integrate_cost(const std::shared_ptr &cost_field, size_t target_x, size_t target_y); + /** + * Calculate the cost integration field starting from a wavefront. + * + * @param cost_field Cost field to integrate. + * @param start_cells Cells flagged as "wavefront blocked" from a LOS pass. + */ + void integrate_cost(const std::shared_ptr &cost_field, + std::vector &&start_cells = {}); + /** * Get the integration field values. * From 1cfac16d47477f183ede60f995e800b8e73f1778 Mon Sep 17 00:00:00 2001 From: heinezen Date: Fri, 8 Mar 2024 01:07:45 +0100 Subject: [PATCH 043/112] path: Use wavefront blocked cells as start for cost integration in demo. --- libopenage/pathfinding/demo/demo_0.cpp | 10 +- libopenage/pathfinding/integration_field.cpp | 117 +++++++++++-------- 2 files changed, 72 insertions(+), 55 deletions(-) diff --git a/libopenage/pathfinding/demo/demo_0.cpp b/libopenage/pathfinding/demo/demo_0.cpp index b8462a7f27..e655598979 100644 --- a/libopenage/pathfinding/demo/demo_0.cpp +++ b/libopenage/pathfinding/demo/demo_0.cpp @@ -44,9 +44,9 @@ void path_demo_0(const util::Path &path) { auto integration_field = std::make_shared(field_length); log::log(INFO << "Created integration field"); - // Set cell (7, 7) to be the target cell - integration_field->integrate_los(cost_field, 7, 7); - integration_field->integrate_cost(cost_field, 7, 7); + // Set cell (7, 7) to be the initial target cell + auto wavefront_blocked = integration_field->integrate_los(cost_field, 7, 7); + integration_field->integrate_cost(cost_field, std::move(wavefront_blocked)); log::log(INFO << "Calculated integration field for target cell (7, 7)"); // Create a flow field from the integration field @@ -83,8 +83,8 @@ void path_demo_0(const util::Path &path) { // Recalculate the integration field and the flow field integration_field->reset(); - integration_field->integrate_los(cost_field, grid_x, grid_y); - integration_field->integrate_cost(cost_field, grid_x, grid_y); + auto wavefront_blocked = integration_field->integrate_los(cost_field, grid_x, grid_y); + integration_field->integrate_cost(cost_field, std::move(wavefront_blocked)); log::log(INFO << "Calculated integration field for target cell (" << grid_x << ", " << grid_y << ")"); diff --git a/libopenage/pathfinding/integration_field.cpp b/libopenage/pathfinding/integration_field.cpp index d871192c36..f3c8901828 100644 --- a/libopenage/pathfinding/integration_field.cpp +++ b/libopenage/pathfinding/integration_field.cpp @@ -40,8 +40,8 @@ std::vector IntegrationField::integrate_los(const std::shared_ptrget_size() << "x" << this->get_size()); - // Store the wavefront cells - std::vector wavefront; + // Store the wavefront_blocked cells + std::vector wavefront_blocked; // Target cell index auto target_idx = target_x + target_y * this->size; @@ -50,68 +50,85 @@ std::vector IntegrationField::integrate_los(const std::shared_ptr found; found.reserve(this->size * this->size); - // Cells that still have to be visited - std::deque open_list; + // Cells that still have to be visited by the current wave + std::deque current_wave; - // Move outwards from the target cell, updating the integration field - open_list.push_back(target_idx); - while (!open_list.empty()) { - auto idx = open_list.front(); - open_list.pop_front(); + // Cells that have to be visited in the next wave + std::deque next_wave; - if (found.contains(idx)) { - // Skip cells that have already been found - continue; - } - else if (this->cells[idx].flags & INTEGRATE_WAVEFRONT_BLOCKED_MASK) { - // Skip cells that are blocked by a LOS corner - continue; - } + // Cost of the current wave + integrated_cost_t cost = INTEGRATED_COST_START; - // Add the current cell to the found cells - found.insert(idx); + // Move outwards from the target cell, updating the integration field + current_wave.push_back(target_idx); + do { + while (!current_wave.empty()) { + auto idx = current_wave.front(); + current_wave.pop_front(); + + if (found.contains(idx)) { + // Skip cells that have already been found + continue; + } + else if (this->cells[idx].flags & INTEGRATE_WAVEFRONT_BLOCKED_MASK) { + // Skip cells that are blocked by a LOS corner + this->cells[idx].cost = cost - 1 + cost_field->get_cost(idx); + continue; + } - // Get the x and y coordinates of the current cell - auto x = idx % this->size; - auto y = idx / this->size; + // Add the current cell to the found cells + found.insert(idx); + + // Get the x and y coordinates of the current cell + auto x = idx % this->size; + auto y = idx / this->size; - // Get the cost of the current cell - auto cell_cost = cost_field->get_cost(idx); + // Get the cost of the current cell + auto cell_cost = cost_field->get_cost(idx); - if (cell_cost > COST_MIN) { - // check each neighbor for a corner - auto corners = this->get_los_corners(cost_field, target_x, target_y, x, y); + if (cell_cost > COST_MIN) { + // check each neighbor for a corner + auto corners = this->get_los_corners(cost_field, target_x, target_y, x, y); - for (auto &corner : corners) { - auto blocked_cells = this->bresenhams_line(target_x, target_y, corner.first, corner.second); - for (auto &blocked_idx : blocked_cells) { - this->cells[blocked_idx].flags |= INTEGRATE_WAVEFRONT_BLOCKED_MASK; + for (auto &corner : corners) { + auto blocked_cells = this->bresenhams_line(target_x, target_y, corner.first, corner.second); + for (auto &blocked_idx : blocked_cells) { + // TODO: stop if blocked_idx is impassable + this->cells[blocked_idx].flags |= INTEGRATE_WAVEFRONT_BLOCKED_MASK; + } + wavefront_blocked.insert(wavefront_blocked.end(), blocked_cells.begin(), blocked_cells.end()); } - wavefront.insert(wavefront.end(), blocked_cells.begin(), blocked_cells.end()); - } - continue; - } + continue; + } - // Set the LOS flag of the current cell - this->cells[idx].flags |= INTEGRATE_LOS_MASK; + // The cell is in the line of sight at min cost + // Set the LOS flag and cost + this->cells[idx].cost = cost; + this->cells[idx].flags |= INTEGRATE_LOS_MASK; - // Search the neighbors of the current cell - if (y > 0) { - open_list.push_back(idx - this->size); - } - if (x > 0) { - open_list.push_back(idx - 1); - } - if (y < this->size - 1) { - open_list.push_back(idx + this->size); - } - if (x < this->size - 1) { - open_list.push_back(idx + 1); + // Search the neighbors of the current cell + if (y > 0) { + next_wave.push_back(idx - this->size); + } + if (x > 0) { + next_wave.push_back(idx - 1); + } + if (y < this->size - 1) { + next_wave.push_back(idx + this->size); + } + if (x < this->size - 1) { + next_wave.push_back(idx + 1); + } } + + // increment the cost and advance the wavefront outwards + cost += 1; + current_wave.swap(next_wave); } + while (!current_wave.empty()); - return wavefront; + return wavefront_blocked; } void IntegrationField::integrate_cost(const std::shared_ptr &cost_field, From f9e4007e1ff49585185555399f8fcbb3a7d1e0a1 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sat, 9 Mar 2024 11:21:51 +0100 Subject: [PATCH 044/112] path: Fix initial wavefront hit not being considered for cost. --- libopenage/pathfinding/integration_field.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/libopenage/pathfinding/integration_field.cpp b/libopenage/pathfinding/integration_field.cpp index f3c8901828..f412f73657 100644 --- a/libopenage/pathfinding/integration_field.cpp +++ b/libopenage/pathfinding/integration_field.cpp @@ -87,6 +87,13 @@ std::vector IntegrationField::integrate_los(const std::shared_ptrget_cost(idx); if (cell_cost > COST_MIN) { + if (cell_cost != COST_IMPASSABLE) { + // Add the current cell to the blocked wavefront if it's not a wall + wavefront_blocked.push_back(idx); + this->cells[idx].flags |= INTEGRATE_WAVEFRONT_BLOCKED_MASK; + this->cells[idx].cost = cost - 1 + cost_field->get_cost(idx); + } + // check each neighbor for a corner auto corners = this->get_los_corners(cost_field, target_x, target_y, x, y); @@ -98,7 +105,6 @@ std::vector IntegrationField::integrate_los(const std::shared_ptrcells.at(idx).flags & INTEGRATE_LOS_MASK) { - // // If the cell is part of the LOS, we don't need to update it - // return; - // } - auto cost = integrated_cost + cell_cost; if (cost < this->cells.at(idx).cost) { // If the new integration value is smaller than the current one, From 3b631136ad9a41ac3531a78bc99eb7e909454eae Mon Sep 17 00:00:00 2001 From: heinezen Date: Sat, 9 Mar 2024 12:49:32 +0100 Subject: [PATCH 045/112] path: Fix cost calculation for wavefront blocked cells. --- libopenage/pathfinding/integration_field.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/libopenage/pathfinding/integration_field.cpp b/libopenage/pathfinding/integration_field.cpp index f412f73657..88778bd5e1 100644 --- a/libopenage/pathfinding/integration_field.cpp +++ b/libopenage/pathfinding/integration_field.cpp @@ -71,8 +71,9 @@ std::vector IntegrationField::integrate_los(const std::shared_ptrcells[idx].flags & INTEGRATE_WAVEFRONT_BLOCKED_MASK) { - // Skip cells that are blocked by a LOS corner + // Stop at cells that are blocked by a LOS corner this->cells[idx].cost = cost - 1 + cost_field->get_cost(idx); + found.insert(idx); continue; } @@ -90,7 +91,7 @@ std::vector IntegrationField::integrate_los(const std::shared_ptrcells[idx].flags |= INTEGRATE_WAVEFRONT_BLOCKED_MASK; + // this->cells[idx].flags |= INTEGRATE_WAVEFRONT_BLOCKED_MASK; this->cells[idx].cost = cost - 1 + cost_field->get_cost(idx); } @@ -100,7 +101,10 @@ std::vector IntegrationField::integrate_los(const std::shared_ptrbresenhams_line(target_x, target_y, corner.first, corner.second); for (auto &blocked_idx : blocked_cells) { - // TODO: stop if blocked_idx is impassable + if (cost_field->get_cost(blocked_idx) == COST_IMPASSABLE) { + // stop if blocked_idx is impassable + break; + } this->cells[blocked_idx].flags |= INTEGRATE_WAVEFRONT_BLOCKED_MASK; } wavefront_blocked.insert(wavefront_blocked.end(), blocked_cells.begin(), blocked_cells.end()); From 88f42e5d90297c84ca74fd1e8f81239f8172ba4c Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 10 Mar 2024 18:46:41 +0100 Subject: [PATCH 046/112] path: Increase precision of wavefront blocked lines. --- libopenage/pathfinding/integration_field.cpp | 34 ++++++++++++-------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/libopenage/pathfinding/integration_field.cpp b/libopenage/pathfinding/integration_field.cpp index 88778bd5e1..303591acc5 100644 --- a/libopenage/pathfinding/integration_field.cpp +++ b/libopenage/pathfinding/integration_field.cpp @@ -88,24 +88,30 @@ std::vector IntegrationField::integrate_los(const std::shared_ptrget_cost(idx); if (cell_cost > COST_MIN) { + // cell blocks line of sight + // and we have to check for corners if (cell_cost != COST_IMPASSABLE) { // Add the current cell to the blocked wavefront if it's not a wall wavefront_blocked.push_back(idx); - // this->cells[idx].flags |= INTEGRATE_WAVEFRONT_BLOCKED_MASK; this->cells[idx].cost = cost - 1 + cost_field->get_cost(idx); } // check each neighbor for a corner auto corners = this->get_los_corners(cost_field, target_x, target_y, x, y); - for (auto &corner : corners) { + // draw a line from the corner to the edge of the field + // to get the cells blocked by the corner auto blocked_cells = this->bresenhams_line(target_x, target_y, corner.first, corner.second); for (auto &blocked_idx : blocked_cells) { - if (cost_field->get_cost(blocked_idx) == COST_IMPASSABLE) { + if (cost_field->get_cost(blocked_idx) > COST_MIN) { // stop if blocked_idx is impassable break; } + // set the blocked flag for the cell this->cells[blocked_idx].flags |= INTEGRATE_WAVEFRONT_BLOCKED_MASK; + + // clear los flag if it was set + this->cells[blocked_idx].flags &= ~INTEGRATE_LOS_MASK; } wavefront_blocked.insert(wavefront_blocked.end(), blocked_cells.begin(), blocked_cells.end()); } @@ -403,14 +409,16 @@ std::vector IntegrationField::bresenhams_line(int target_x, double dy = std::abs(ty - corner_y); auto m = dy / dx; - auto error = 0.5; + auto error = m; // Check which direction the line is going if (corner_x < tx) { if (corner_y < ty) { // left and up y -= 1; - while (x > 0 and y > 0) { + x -= 1; + while (x >= 0 and y >= 0) { + cells.push_back(x + y * this->size); if (error > 1.0) { y -= 1; error -= 1.0; @@ -419,12 +427,13 @@ std::vector IntegrationField::bresenhams_line(int target_x, x -= 1; error += m; } - cells.push_back(x + y * this->size); } } else { // left and down - while (x > 0 and y < this->size - 1) { + x -= 1; + while (x >= 0 and y < this->size) { + cells.push_back(x + y * this->size); if (error > 1.0) { y += 1; error -= 1.0; @@ -433,16 +442,15 @@ std::vector IntegrationField::bresenhams_line(int target_x, x -= 1; error += m; } - cells.push_back(x + y * this->size); } } } else { if (corner_y < ty) { // right and up - x -= 1; y -= 1; - while (x < this->size - 1 and y > 0) { + while (x < this->size and y >= 0) { + cells.push_back(x + y * this->size); if (error > 1.0) { y -= 1; error -= 1.0; @@ -451,13 +459,12 @@ std::vector IntegrationField::bresenhams_line(int target_x, x += 1; error += m; } - cells.push_back(x + y * this->size); } } else { // right and down - x -= 1; - while (x < this->size - 1 and y < this->size - 1) { + while (x < this->size and y < this->size) { + cells.push_back(x + y * this->size); if (error > 1.0) { y += 1; error -= 1.0; @@ -466,7 +473,6 @@ std::vector IntegrationField::bresenhams_line(int target_x, x += 1; error += m; } - cells.push_back(x + y * this->size); } } } From e3e420d4930645f99e5dcffc12a1ef2d879ac573 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 10 Mar 2024 19:02:52 +0100 Subject: [PATCH 047/112] path: Document our bresenham's line algorithm implementation. --- libopenage/pathfinding/integration_field.cpp | 69 +++++++++++--------- libopenage/pathfinding/integration_field.h | 25 ++++--- 2 files changed, 56 insertions(+), 38 deletions(-) diff --git a/libopenage/pathfinding/integration_field.cpp b/libopenage/pathfinding/integration_field.cpp index 303591acc5..09cebb0f4d 100644 --- a/libopenage/pathfinding/integration_field.cpp +++ b/libopenage/pathfinding/integration_field.cpp @@ -401,76 +401,85 @@ std::vector IntegrationField::bresenhams_line(int target_x, int corner_y) { std::vector cells; - auto x = corner_x; - auto y = corner_y; + // cell coordinates + // these have to be offset depending on the line direction + auto cell_x = corner_x; + auto cell_y = corner_y; + + // field edge boundary + int boundary = this->size; + + // target coordinates + // offset by 0.5 to get the center of the cell double tx = target_x + 0.5; double ty = target_y + 0.5; + + // slope of the line double dx = std::abs(tx - corner_x); double dy = std::abs(ty - corner_y); auto m = dy / dx; + // error margin for the line + // if the error is greater than 1.0, we have to move in the y direction auto error = m; // Check which direction the line is going if (corner_x < tx) { - if (corner_y < ty) { - // left and up - y -= 1; - x -= 1; - while (x >= 0 and y >= 0) { - cells.push_back(x + y * this->size); + if (corner_y < ty) { // left and up + cell_y -= 1; + cell_x -= 1; + while (cell_x >= 0 and cell_y >= 0) { + cells.push_back(cell_x + cell_y * this->size); if (error > 1.0) { - y -= 1; + cell_y -= 1; error -= 1.0; } else { - x -= 1; + cell_x -= 1; error += m; } } } - else { - // left and down - x -= 1; - while (x >= 0 and y < this->size) { - cells.push_back(x + y * this->size); + + else { // left and down + cell_x -= 1; + while (cell_x >= 0 and cell_y < boundary) { + cells.push_back(cell_x + cell_y * this->size); if (error > 1.0) { - y += 1; + cell_y += 1; error -= 1.0; } else { - x -= 1; + cell_x -= 1; error += m; } } } } else { - if (corner_y < ty) { - // right and up - y -= 1; - while (x < this->size and y >= 0) { - cells.push_back(x + y * this->size); + if (corner_y < ty) { // right and up + cell_y -= 1; + while (cell_x < boundary and cell_y >= 0) { + cells.push_back(cell_x + cell_y * this->size); if (error > 1.0) { - y -= 1; + cell_y -= 1; error -= 1.0; } else { - x += 1; + cell_x += 1; error += m; } } } - else { - // right and down - while (x < this->size and y < this->size) { - cells.push_back(x + y * this->size); + else { // right and down + while (cell_x < boundary and cell_y < boundary) { + cells.push_back(cell_x + cell_y * this->size); if (error > 1.0) { - y += 1; + cell_y += 1; error -= 1.0; } else { - x += 1; + cell_x += 1; error += m; } } diff --git a/libopenage/pathfinding/integration_field.h b/libopenage/pathfinding/integration_field.h index 43621f1959..dc5821c856 100644 --- a/libopenage/pathfinding/integration_field.h +++ b/libopenage/pathfinding/integration_field.h @@ -106,6 +106,8 @@ class IntegrationField { * @param idx Index of the neighbor cell that is updated. * @param cell_cost Cost of the neighbor cell from the cost field. * @param integrated_cost Current integrated cost of the updating cell in the integration field. + * @param open_list List of cells to be updated. + * @param in_list Set of cells that have been updated. * * @return New integration value of the cell. */ @@ -119,10 +121,12 @@ class IntegrationField { * Get the LOS corners around a cell. * * @param cost_field Cost field to integrate. - * @param target_x X coordinate of the target. - * @param target_y Y coordinate of the target. - * @param blocker_x X coordinate of the cell blocking LOS. - * @param blocker_y Y coordinate of the cell blocking LOS. + * @param target_x X cell coordinate of the target. + * @param target_y Y cell coordinate of the target. + * @param blocker_x X cell coordinate of the cell blocking LOS. + * @param blocker_y Y cell coordinate of the cell blocking LOS. + * + * @return Field coordinates of the LOS corners. */ std::vector> get_los_corners(const std::shared_ptr &cost_field, size_t target_x, @@ -133,10 +137,15 @@ class IntegrationField { /** * Get the cells in a bresenham's line between the corner cell and the field edge. * - * @param target_x X coordinate of the target. - * @param target_y Y coordinate of the target. - * @param corner_x X coordinate edge of the LOS corner. - * @param corner_y Y coordinate edge of the LOS corner. + * This function is a modified version of the bresenham's line algorithm that + * retrieves the cells between the corner point and the field's edge, rather than + * the cells between two arbitrary points. We do this because the intersection + * point with the field edge is unknown. + * + * @param target_x X cell coordinate of the target. + * @param target_y Y cell coordinate of the target. + * @param corner_x X field coordinate edge of the LOS corner. + * @param corner_y Y field coordinate edge of the LOS corner. */ std::vector bresenhams_line(int target_x, int target_y, From 897bfe108fe9a083758817c8407a3fcc3cf015eb Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 10 Mar 2024 19:27:12 +0100 Subject: [PATCH 048/112] path: Add LOS pass to integrator. --- libopenage/pathfinding/flow_field.cpp | 4 +++ libopenage/pathfinding/integrator.cpp | 4 +-- libopenage/pathfinding/tests.cpp | 48 ++++++++++++++++++--------- 3 files changed, 38 insertions(+), 18 deletions(-) diff --git a/libopenage/pathfinding/flow_field.cpp b/libopenage/pathfinding/flow_field.cpp index 64e7d82d23..52f4d7738e 100644 --- a/libopenage/pathfinding/flow_field.cpp +++ b/libopenage/pathfinding/flow_field.cpp @@ -56,6 +56,10 @@ void FlowField::build(const std::shared_ptr &integrate_field) if (integrate_cells[idx].flags & INTEGRATE_LOS_MASK) { // Cell is in line of sight this->cells[idx] = this->cells[idx] | FLOW_LOS_MASK; + + // we can skip calculating the flow direction as we can + // move straight to the target from this cell + this->cells[idx] = this->cells[idx] | FLOW_PATHABLE_MASK; continue; } diff --git a/libopenage/pathfinding/integrator.cpp b/libopenage/pathfinding/integrator.cpp index cbff7fd7cb..2c083b5615 100644 --- a/libopenage/pathfinding/integrator.cpp +++ b/libopenage/pathfinding/integrator.cpp @@ -17,8 +17,8 @@ std::shared_ptr Integrator::build(size_t target_x, size_t target_y) { auto flow_field = std::make_shared(this->cost_field->get_size()); auto integrate_field = std::make_shared(this->cost_field->get_size()); - integrate_field->integrate_los(this->cost_field, target_x, target_y); - integrate_field->integrate_cost(this->cost_field, target_x, target_y); + auto wavefront_blocked = integrate_field->integrate_los(this->cost_field, target_x, target_y); + integrate_field->integrate_cost(this->cost_field, std::move(wavefront_blocked)); flow_field->build(integrate_field); return flow_field; diff --git a/libopenage/pathfinding/tests.cpp b/libopenage/pathfinding/tests.cpp index 7b15a4406a..4cd6d40363 100644 --- a/libopenage/pathfinding/tests.cpp +++ b/libopenage/pathfinding/tests.cpp @@ -327,22 +327,6 @@ void flow_field() { // | 1 | 1 | 1 | cost_field->set_costs({1, 1, 1, 1, 255, 1, 1, 1, 1}); - // The flow field for targeting (2, 2) hould look like this: - // | E | SE | S | - // | SE | X | S | - // | E | E | N | - auto ff_expected = std::vector{ - FLOW_PATHABLE_MASK | static_cast(flow_dir_t::EAST), - FLOW_PATHABLE_MASK | static_cast(flow_dir_t::SOUTH_EAST), - FLOW_PATHABLE_MASK | static_cast(flow_dir_t::SOUTH), - FLOW_PATHABLE_MASK | static_cast(flow_dir_t::SOUTH_EAST), - 0, - FLOW_PATHABLE_MASK | static_cast(flow_dir_t::SOUTH), - FLOW_PATHABLE_MASK | static_cast(flow_dir_t::EAST), - FLOW_PATHABLE_MASK | static_cast(flow_dir_t::EAST), - FLOW_PATHABLE_MASK | static_cast(flow_dir_t::NORTH), - }; - // Test the different field types { auto integration_field = std::make_shared(3); @@ -365,6 +349,22 @@ void flow_field() { 0, }; + // The flow field for targeting (2, 2) hould look like this: + // | E | SE | S | + // | SE | X | S | + // | E | E | N | + auto ff_expected = std::vector{ + FLOW_PATHABLE_MASK | static_cast(flow_dir_t::EAST), + FLOW_PATHABLE_MASK | static_cast(flow_dir_t::SOUTH_EAST), + FLOW_PATHABLE_MASK | static_cast(flow_dir_t::SOUTH), + FLOW_PATHABLE_MASK | static_cast(flow_dir_t::SOUTH_EAST), + 0, + FLOW_PATHABLE_MASK | static_cast(flow_dir_t::SOUTH), + FLOW_PATHABLE_MASK | static_cast(flow_dir_t::EAST), + FLOW_PATHABLE_MASK | static_cast(flow_dir_t::EAST), + FLOW_PATHABLE_MASK | static_cast(flow_dir_t::NORTH), + }; + // Compare the integration field cells with the expected values for (size_t i = 0; i < int_cells.size(); i++) { TESTEQUALS(int_cells[i].cost, int_expected[i]); @@ -391,6 +391,22 @@ void flow_field() { auto flow_field = integrator->build(2, 2); auto ff_cells = flow_field->get_cells(); + // The flow field for targeting (2, 2) hould look like this: + // | E | SE | S | + // | SE | X | S | + // | E | E | N | + auto ff_expected = std::vector{ + FLOW_PATHABLE_MASK | static_cast(flow_dir_t::EAST), + FLOW_WAVEFRONT_BLOCKED_MASK | FLOW_PATHABLE_MASK | static_cast(flow_dir_t::SOUTH_EAST), + FLOW_LOS_MASK | FLOW_PATHABLE_MASK, + FLOW_WAVEFRONT_BLOCKED_MASK | FLOW_PATHABLE_MASK | static_cast(flow_dir_t::SOUTH_EAST), + 0, + FLOW_LOS_MASK | FLOW_PATHABLE_MASK, + FLOW_LOS_MASK | FLOW_PATHABLE_MASK, + FLOW_LOS_MASK | FLOW_PATHABLE_MASK, + FLOW_LOS_MASK | FLOW_PATHABLE_MASK, + }; + // Compare the flow field cells with the expected values for (size_t i = 0; i < ff_cells.size(); i++) { TESTEQUALS(ff_cells[i], ff_expected[i]); From b693ea1bc295e05ada51a406c32cc8be0ddd59cf Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 17 Mar 2024 01:33:46 +0100 Subject: [PATCH 049/112] path: Grid for multiple fields. --- libopenage/pathfinding/CMakeLists.txt | 1 + libopenage/pathfinding/grid.cpp | 20 +++++++++ libopenage/pathfinding/grid.h | 64 +++++++++++++++++++++++++++ 3 files changed, 85 insertions(+) create mode 100644 libopenage/pathfinding/grid.cpp create mode 100644 libopenage/pathfinding/grid.h diff --git a/libopenage/pathfinding/CMakeLists.txt b/libopenage/pathfinding/CMakeLists.txt index 917cae429a..8c7531dd16 100644 --- a/libopenage/pathfinding/CMakeLists.txt +++ b/libopenage/pathfinding/CMakeLists.txt @@ -3,6 +3,7 @@ add_sources(libopenage cost_field.cpp definitions.cpp flow_field.cpp + grid.cpp heuristics.cpp integration_field.cpp integrator.cpp diff --git a/libopenage/pathfinding/grid.cpp b/libopenage/pathfinding/grid.cpp new file mode 100644 index 0000000000..fc3424b333 --- /dev/null +++ b/libopenage/pathfinding/grid.cpp @@ -0,0 +1,20 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#include "grid.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}} { +} + +std::pair CostGrid::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]; +} + +} // namespace openage::path diff --git a/libopenage/pathfinding/grid.h b/libopenage/pathfinding/grid.h new file mode 100644 index 0000000000..19eb7d682c --- /dev/null +++ b/libopenage/pathfinding/grid.h @@ -0,0 +1,64 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#pragma once + +#include +#include +#include + +#include "pathfinding/cost_field.h" + + +namespace openage::path { + +/** + * Grid of cost fields for flow field pathfinding. + */ +class CostGrid { +public: + /** + * Create a grid with a specified size and field size. + * + * @param width Width of the grid. + * @param height Height of the grid. + * @param field_size Size of the cost fields. + */ + CostGrid(size_t width, + size_t height, + size_t field_size); + + /** + * Get the size of the grid. + * + * @return Size of the grid (width x height). + */ + std::pair get_size() const; + + /** + * Get the cost field at a specified position. + * + * @param x X coordinate. + * @param y Y coordinate. + * @return Cost field at the specified position. + */ + CostField &get_field(size_t x, size_t y); + +private: + /** + * Width of the grid. + */ + size_t width; + + /** + * Height of the grid. + */ + size_t height; + + /** + * Cost fields. + */ + std::vector fields; +}; + + +} // namespace openage::path From 4278de9eeae55499d1cc9d7d0fad84de9c6d9e01 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 17 Mar 2024 16:11:24 +0100 Subject: [PATCH 050/112] path: Portals between fields. --- libopenage/pathfinding/CMakeLists.txt | 1 + libopenage/pathfinding/portal.cpp | 104 ++++++++++++++ libopenage/pathfinding/portal.h | 196 ++++++++++++++++++++++++++ 3 files changed, 301 insertions(+) create mode 100644 libopenage/pathfinding/portal.cpp create mode 100644 libopenage/pathfinding/portal.h diff --git a/libopenage/pathfinding/CMakeLists.txt b/libopenage/pathfinding/CMakeLists.txt index 8c7531dd16..c5d1b7aaa4 100644 --- a/libopenage/pathfinding/CMakeLists.txt +++ b/libopenage/pathfinding/CMakeLists.txt @@ -8,6 +8,7 @@ add_sources(libopenage integration_field.cpp integrator.cpp path.cpp + portal.cpp tests.cpp types.cpp ) diff --git a/libopenage/pathfinding/portal.cpp b/libopenage/pathfinding/portal.cpp new file mode 100644 index 0000000000..b959cbdc6c --- /dev/null +++ b/libopenage/pathfinding/portal.cpp @@ -0,0 +1,104 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#include "portal.h" + +#include "error/error.h" + + +namespace openage::path { + +Portal::Portal(std::shared_ptr sector0, + std::shared_ptr sector1, + std::vector> sector0_exits, + std::vector> sector1_exits, + PortalDirection direction, + size_t cell_start_x, + size_t cell_start_y, + size_t cell_end_x, + size_t cell_end_y) : + sector0{sector0}, + sector1{sector1}, + sector0_exits{sector0_exits}, + sector1_exits{sector1_exits}, + direction{direction}, + cell_start_x{cell_start_x}, + cell_start_y{cell_start_y}, + cell_end_x{cell_end_x}, + cell_end_y{cell_end_y} { +} + +const std::vector> &Portal::get_exits(const std::shared_ptr &entry_sector) const { + ENSURE(entry_sector == this->sector0 || entry_sector == this->sector1, "Invalid entry sector"); + + if (entry_sector == this->sector0) { + return this->sector1_exits; + } + return this->sector0_exits; +} + +const std::shared_ptr &Portal::get_exit_sector(const std::shared_ptr &entry_sector) const { + ENSURE(entry_sector == this->sector0 || entry_sector == this->sector1, "Invalid entry sector"); + + if (entry_sector == this->sector0) { + return this->sector1; + } + return this->sector0; +} + +std::pair Portal::get_entry_start(const std::shared_ptr &entry_sector) const { + ENSURE(entry_sector == this->sector0 || entry_sector == this->sector1, "Invalid entry sector"); + + if (entry_sector == this->sector0) { + return this->get_sector0_start(); + } + + return this->get_sector1_start(); +} + +std::pair Portal::get_entry_end(const std::shared_ptr &entry_sector) const { + ENSURE(entry_sector == this->sector0 || entry_sector == this->sector1, "Invalid entry sector"); + + if (entry_sector == this->sector0) { + return this->get_sector0_end(); + } + + return this->get_sector1_end(); +} + +std::pair Portal::get_exit_start(const std::shared_ptr &entry_sector) const { + ENSURE(entry_sector == this->sector0 || entry_sector == this->sector1, "Invalid entry sector"); + + if (entry_sector == this->sector0) { + return this->get_sector1_start(); + } + + return this->get_sector0_start(); +} + +std::pair Portal::get_exit_end(const std::shared_ptr &entry_sector) const { + ENSURE(entry_sector == this->sector0 || entry_sector == this->sector1, "Invalid entry sector"); + + if (entry_sector == this->sector0) { + return this->get_sector1_end(); + } + + return this->get_sector0_end(); +} + +std::pair Portal::get_sector0_start() const { + return {this->cell_start_x, this->cell_start_y}; +} + +std::pair Portal::get_sector0_end() const { + return {this->cell_end_x, this->cell_end_y}; +} + +std::pair Portal::get_sector1_start() const { + return {this->cell_end_x, this->cell_end_y}; +} + +std::pair Portal::get_sector1_end() const { + return {this->cell_start_x, this->cell_start_y}; +} + +} // namespace openage::path diff --git a/libopenage/pathfinding/portal.h b/libopenage/pathfinding/portal.h new file mode 100644 index 0000000000..3632e3cecd --- /dev/null +++ b/libopenage/pathfinding/portal.h @@ -0,0 +1,196 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#pragma once + + +#include + +#include "pathfinding/cost_field.h" + + +namespace openage::path { +class CostField; + +/** + * Possible directions of a portal node. + */ +enum class PortalDirection { + NORTH_SOUTH, + EAST_WEST +}; + +/** + * Biderectional gateway for connecting two sectors in the flow field pathfinder. + * + * Portals are located at the border of two sectors (0 and 1), and allow units to move between them. + * For each of these sectors, the portal stores the start and end coordinates where the + * sectors overlap as well as the other portals that can be reached in the same + * sector. For simplicity, the portal is assumed to be a straight line of cells from the start + * to the end. + * + * The portal is bidirectional, meaning that it can be entered from either sector and + * exited into the other sector. The direction of the portal from one sector to the other + * is stored in the portal node. As a convention and to simplify computations, sector 0 must be + * the either the north or east sector on the grid in relation to sector 1. + */ +class Portal { +public: + /** + * Create a new portal between two sectors. + * + * As a convention, sector 0 must be the either the north or east sector + * on the grid in relation to sector 1. + * + * @param sector0 First sector connected by the 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, + PortalDirection direction, + size_t cell_start_x, + size_t cell_start_y, + size_t cell_end_x, + size_t cell_end_y); + + ~Portal() = default; + + /** + * 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 reachable from the portal. + */ + const std::vector> &get_exits(const std::shared_ptr &entry_sector) const; + + /** + * Get the cost field of the sector where the portal is exited. + * + * @param entry_sector Sector from which the portal is entered. + * + * @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; + + /** + * Get the cost field of the sector from which the portal is entered. + * + * @param entry_sector Sector from which the portal is entered. + * + * @return Cost field of the sector from which the portal is entered. + */ + std::pair get_entry_start(const std::shared_ptr &entry_sector) const; + + /** + * Get the cell coordinates of the start of the portal in the entry sector. + * + * @param entry_sector Sector from which the portal is entered. + * + * @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; + + /** + * Get the cell coordinates of the start of the portal in the exit sector. + * + * @param entry_sector Sector from which the portal is entered. + * + * @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; + + /** + * Get the cell coordinates of the end of the portal in the exit sector. + * + * @param entry_sector Sector from which the portal is entered. + * + * @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; + +private: + /** + * Get the start cell coordinates of the portal. + * + * @return Start cell coordinates of the portal. + */ + std::pair get_sector0_start() const; + + /** + * Get the end cell coordinates of the portal. + * + * @return End cell coordinates of the portal. + */ + std::pair get_sector0_end() const; + + /** + * Get the start cell coordinates of the portal. + * + * @return Start cell coordinates of the portal. + */ + std::pair get_sector1_start() const; + + /** + * Get the end cell coordinates of the portal. + * + * @return End cell coordinates of the portal. + */ + std::pair get_sector1_end() const; + + /** + * Sector 0 + */ + std::shared_ptr sector0; + + /** + * Sector 1 + */ + std::shared_ptr sector1; + + /** + * Exits from sector 0 + */ + std::vector> sector0_exits; + + /** + * Exits from sector 1 + */ + std::vector> sector1_exits; + + /** + * Direction of the portal + */ + PortalDirection direction; + + /** + * Start cell x coordinate + */ + size_t cell_start_x; + + /** + * Start cell y coordinate + */ + size_t cell_start_y; + + /** + * End cell x coordinate + */ + size_t cell_end_x; + + /** + * End cell y coordinate + */ + size_t cell_end_y; +}; +} // namespace openage::path From a92b93d5751979a19c1ce68fb443033473c53afb Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 17 Mar 2024 18:35:28 +0100 Subject: [PATCH 051/112] 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 | 113 ++++++++++++++++++++++++++ libopenage/pathfinding/sector.h | 108 ++++++++++++++++++++++++ libopenage/pathfinding/types.h | 6 ++ 8 files changed, 340 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..8c5221821d --- /dev/null +++ b/libopenage/pathfinding/sector.cpp @@ -0,0 +1,113 @@ +// 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 From 1f48794edf47fc0f303010abb2f799e54aa817da Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 17 Mar 2024 19:23:38 +0100 Subject: [PATCH 052/112] path: Move old path implementation to 'legacy' folder. --- libopenage/pathfinding/CMakeLists.txt | 4 +- libopenage/pathfinding/legacy/CMakeLists.txt | 6 + .../pathfinding/{ => legacy}/a_star.cpp | 10 +- libopenage/pathfinding/{ => legacy}/a_star.h | 0 .../pathfinding/{ => legacy}/heuristics.cpp | 0 .../pathfinding/{ => legacy}/heuristics.h | 2 +- libopenage/pathfinding/{ => legacy}/path.cpp | 2 +- libopenage/pathfinding/{ => legacy}/path.h | 10 +- .../pathfinding/{ => legacy}/path_utils.h | 0 libopenage/pathfinding/legacy/tests.cpp | 317 +++++++++++++++++ libopenage/pathfinding/tests.cpp | 320 +----------------- 11 files changed, 344 insertions(+), 327 deletions(-) create mode 100644 libopenage/pathfinding/legacy/CMakeLists.txt rename libopenage/pathfinding/{ => legacy}/a_star.cpp (96%) rename libopenage/pathfinding/{ => legacy}/a_star.h (100%) rename libopenage/pathfinding/{ => legacy}/heuristics.cpp (100%) rename libopenage/pathfinding/{ => legacy}/heuristics.h (97%) rename libopenage/pathfinding/{ => legacy}/path.cpp (98%) rename libopenage/pathfinding/{ => legacy}/path.h (96%) rename libopenage/pathfinding/{ => legacy}/path_utils.h (100%) create mode 100644 libopenage/pathfinding/legacy/tests.cpp diff --git a/libopenage/pathfinding/CMakeLists.txt b/libopenage/pathfinding/CMakeLists.txt index 55a3f19ee0..84e7374203 100644 --- a/libopenage/pathfinding/CMakeLists.txt +++ b/libopenage/pathfinding/CMakeLists.txt @@ -1,13 +1,10 @@ add_sources(libopenage - a_star.cpp cost_field.cpp definitions.cpp flow_field.cpp grid.cpp - heuristics.cpp integration_field.cpp integrator.cpp - path.cpp portal.cpp sector.cpp tests.cpp @@ -15,3 +12,4 @@ add_sources(libopenage ) add_subdirectory("demo") +add_subdirectory("legacy") diff --git a/libopenage/pathfinding/legacy/CMakeLists.txt b/libopenage/pathfinding/legacy/CMakeLists.txt new file mode 100644 index 0000000000..ad828995c9 --- /dev/null +++ b/libopenage/pathfinding/legacy/CMakeLists.txt @@ -0,0 +1,6 @@ +add_sources(libopenage + a_star.cpp + heuristics.cpp + path.cpp + tests.cpp +) diff --git a/libopenage/pathfinding/a_star.cpp b/libopenage/pathfinding/legacy/a_star.cpp similarity index 96% rename from libopenage/pathfinding/a_star.cpp rename to libopenage/pathfinding/legacy/a_star.cpp index 6fc4a53c8c..640a20bb32 100644 --- a/libopenage/pathfinding/a_star.cpp +++ b/libopenage/pathfinding/legacy/a_star.cpp @@ -14,11 +14,11 @@ #include -#include "../datastructure/pairing_heap.h" -#include "../log/log.h" -#include "../util/strings.h" -#include "heuristics.h" -#include "path.h" +#include "datastructure/pairing_heap.h" +#include "log/log.h" +#include "pathfinding/legacy/heuristics.h" +#include "pathfinding/legacy/path.h" +#include "util/strings.h" namespace openage { diff --git a/libopenage/pathfinding/a_star.h b/libopenage/pathfinding/legacy/a_star.h similarity index 100% rename from libopenage/pathfinding/a_star.h rename to libopenage/pathfinding/legacy/a_star.h diff --git a/libopenage/pathfinding/heuristics.cpp b/libopenage/pathfinding/legacy/heuristics.cpp similarity index 100% rename from libopenage/pathfinding/heuristics.cpp rename to libopenage/pathfinding/legacy/heuristics.cpp diff --git a/libopenage/pathfinding/heuristics.h b/libopenage/pathfinding/legacy/heuristics.h similarity index 97% rename from libopenage/pathfinding/heuristics.h rename to libopenage/pathfinding/legacy/heuristics.h index 9d84560f4c..e9e9a03709 100644 --- a/libopenage/pathfinding/heuristics.h +++ b/libopenage/pathfinding/legacy/heuristics.h @@ -2,7 +2,7 @@ #pragma once -#include "path.h" +#include "pathfinding/legacy/path.h" namespace openage { namespace path { diff --git a/libopenage/pathfinding/path.cpp b/libopenage/pathfinding/legacy/path.cpp similarity index 98% rename from libopenage/pathfinding/path.cpp rename to libopenage/pathfinding/legacy/path.cpp index a709ab0648..6db3c528f9 100644 --- a/libopenage/pathfinding/path.cpp +++ b/libopenage/pathfinding/legacy/path.cpp @@ -2,7 +2,7 @@ #include -#include "path.h" +#include "pathfinding/legacy/path.h" namespace openage::path { diff --git a/libopenage/pathfinding/path.h b/libopenage/pathfinding/legacy/path.h similarity index 96% rename from libopenage/pathfinding/path.h rename to libopenage/pathfinding/legacy/path.h index bbe7aa0f62..a6d7aa80ae 100644 --- a/libopenage/pathfinding/path.h +++ b/libopenage/pathfinding/legacy/path.h @@ -7,11 +7,11 @@ #include #include -#include "../coord/phys.h" -#include "../coord/tile.h" -#include "../datastructure/pairing_heap.h" -#include "../util/hash.h" -#include "../util/misc.h" +#include "coord/phys.h" +#include "coord/tile.h" +#include "datastructure/pairing_heap.h" +#include "util/hash.h" +#include "util/misc.h" namespace openage { diff --git a/libopenage/pathfinding/path_utils.h b/libopenage/pathfinding/legacy/path_utils.h similarity index 100% rename from libopenage/pathfinding/path_utils.h rename to libopenage/pathfinding/legacy/path_utils.h diff --git a/libopenage/pathfinding/legacy/tests.cpp b/libopenage/pathfinding/legacy/tests.cpp new file mode 100644 index 0000000000..f68e512660 --- /dev/null +++ b/libopenage/pathfinding/legacy/tests.cpp @@ -0,0 +1,317 @@ +// Copyright 2015-2024 the openage authors. See copying.md for legal info. + +#include "log/log.h" +#include "testing/testing.h" + +#include "pathfinding/legacy/heuristics.h" +#include "pathfinding/legacy/path.h" + +namespace openage { +namespace path { +namespace tests { + +/** + * This function tests setting up basic nodes that point to a previous node. + * Tests that direction is set correctly and that factor is set correctly. + */ +void node_0() { + coord::phys3 p0{0, 0, 0}; + coord::phys3 p1{1, 0, 0}; + coord::phys3 p2{1, 1, 0}; + coord::phys3 p3{1, -1, 0}; + coord::phys3 p4{2, 0, 0}; + coord::phys3 p5{2, 2, 0}; + coord::phys3 p6{2, -2, 0}; + + node_pt n0 = std::make_unique(p0, nullptr); + node_pt n1 = std::make_unique(p1, n0); + node_pt n2 = std::make_unique(p2, n1); + node_pt n3 = std::make_unique(p3, n1); + node_pt n4 = std::make_unique(p0, n1); + + // Testing how the factor is effected from the change in + // direction from one node to another + TESTEQUALS(n1->direction.ne, 1); + TESTEQUALS(n1->direction.se, 0); + + // Expect this to be 2 since the similarity between nodes is zero + TESTEQUALS(n1->factor, 2); + + TESTEQUALS(n2->direction.ne, 0); + TESTEQUALS(n2->direction.se, 1); + + // Expect this to be 2 since it takes a 90 degree turn from n1 + TESTEQUALS(n2->factor, 2); + + TESTEQUALS(n3->direction.ne, 0); + TESTEQUALS(n3->direction.se, -1); + + // Expect this to be 2 since it takes a 90 degree turn from n1 + TESTEQUALS(n3->factor, 2); + + TESTEQUALS(n4->direction.ne, -1); + TESTEQUALS(n4->direction.se, 0); + + // Expect this to be 3 since it takes a 180 degree turn from n1 + TESTEQUALS(n4->factor, 3); + + // Testing that the distance from the previous node noes not + // effect the factor, only change in direction + + n1 = std::make_unique(p4, n0); + n2 = std::make_unique(p5, n1); + n3 = std::make_unique(p6, n1); + n4 = std::make_unique(p0, n1); + + TESTEQUALS(n1->direction.ne, 1); + TESTEQUALS(n1->direction.se, 0); + + // Expect this to be 2 since the similarity between nodes is zero + TESTEQUALS(n1->factor, 2); + TESTEQUALS(n2->direction.ne, 0); + TESTEQUALS(n2->direction.se, 1); + + // Expect this to be 2 since it takes a 90 degree turn from n1 + TESTEQUALS(n2->factor, 2); + TESTEQUALS(n3->direction.ne, 0); + TESTEQUALS(n3->direction.se, -1); + + // Expect this to be 2 since it takes a 90 degree turn from n1 + TESTEQUALS(n3->factor, 2); + TESTEQUALS(n4->direction.ne, -1); + TESTEQUALS(n4->direction.se, 0); + + // Expect this to be 3 since it takes a 180 degree turn from n1 + TESTEQUALS(n4->factor, 3); +} + +/** + * This function tests Node->cost_to. The testing is done on 2 unrelated + * nodes (They have no previous node) to test the basic cost without adding + * the cost from node->factor. + */ +void node_cost_to_0() { + // Testing basic cost_to with ne only + coord::phys3 p0{0, 0, 0}; + coord::phys3 p1{10, 0, 0}; + + node_pt n0 = std::make_unique(p0, nullptr); + node_pt n1 = std::make_unique(p1, nullptr); + + TESTEQUALS(n0->cost_to(*n1), 10); + TESTEQUALS(n1->cost_to(*n0), 10); + + // Testing basic cost_to with se only + coord::phys3 p2{0, 5, 0}; + + node_pt n2 = std::make_unique(p2, nullptr); + + TESTEQUALS(n0->cost_to(*n2), 5); + TESTEQUALS(n2->cost_to(*n0), 5); + + // Testing cost_to with both se and ne: + coord::phys3 p3{3, 4, 0}; // -> sqrt(3*3 + 4*4) == 5 + + node_pt n3 = std::make_unique(p3, nullptr); + TESTEQUALS(n0->cost_to(*n3), 5); + TESTEQUALS(n3->cost_to(*n0), 5); + + // Test cost_to and check that `up` has no effect + coord::phys3 p4{3, 4, 8}; + + node_pt n4 = std::make_unique(p4, nullptr); + + TESTEQUALS(n0->cost_to(*n4), 5); + TESTEQUALS(n4->cost_to(*n0), 5); +} + +/** + * This function tests Node->cost_to. The testing is done on the neighbor + * nodes to test how the directional factor effects the cost. + */ +void node_cost_to_1() { + // Set up coords so that n1 will have a direction of ne = 1 + // but n0 with not be in n1s neighbors + coord::phys3 p0{-0.125, 0, 0}; + coord::phys3 p1{0.125, 0, 0}; + + node_pt n0 = std::make_unique(p0, nullptr); + node_pt n1 = std::make_unique(p1, n0); + + // We expect twice the normal cost since n0 had not direction + // thus we get a factor of 2 on n1 + TESTEQUALS_FLOAT(n0->cost_to(*n1), 0.5, 0.001); + TESTEQUALS_FLOAT(n1->cost_to(*n0), 0.5, 0.001); + + nodemap_t visited_tiles; + visited_tiles[n0->position] = n0; + + // Collect the costs to go to all the neighbors of n1 + std::vector costs; + for (node_pt neighbor : n1->get_neighbors(visited_tiles, 1)) { + costs.push_back(n1->cost_to(*neighbor)); + } + + TESTEQUALS_FLOAT(costs[0], 0.45711, 0.001); + TESTEQUALS_FLOAT(costs[1], 0.25, 0.001); + TESTEQUALS_FLOAT(costs[2], 0.45711, 0.001); + TESTEQUALS_FLOAT(costs[3], 0.5, 0.001); + TESTEQUALS_FLOAT(costs[4], 0.95709, 0.001); + TESTEQUALS_FLOAT(costs[5], 0.75, 0.001); + TESTEQUALS_FLOAT(costs[6], 0.95709, 0.001); + TESTEQUALS_FLOAT(costs[7], 0.5, 0.001); +} + +/** + * This function does a basic test of generating a backtrace from the + * last node in a path. + */ +void node_generate_backtrace_0() { + coord::phys3 p0{0, 0, 0}; + coord::phys3 p1{10, 0, 0}; + coord::phys3 p2{20, 0, 0}; + coord::phys3 p3{30, 0, 0}; + + node_pt n0 = std::make_unique(p0, nullptr); + node_pt n1 = std::make_unique(p1, n0); + node_pt n2 = std::make_unique(p2, n1); + node_pt n3 = std::make_unique(p3, n2); + + Path path = n3->generate_backtrace(); + + (path.waypoints[0] == *n3) or TESTFAIL; + (path.waypoints[1] == *n2) or TESTFAIL; + (path.waypoints[2] == *n1) or TESTFAIL; +} + +/** + * This function tests Node->get_neighbors and how the scale effects + * the neighbors given. + */ +void node_get_neighbors_0() { + coord::phys3 p0{0, 0, 0}; + + node_pt n0 = std::make_unique(p0, nullptr); + nodemap_t map; + + // Testing get_neighbors returning all surounding tiles with + // a factor of 1 + + std::vector neighbors = n0->get_neighbors(map, 1); + TESTEQUALS(neighbors.size(), 8); + + TESTEQUALS_FLOAT(neighbors[0]->position.ne.to_double(), 0.125, 0.001); + TESTEQUALS_FLOAT(neighbors[0]->position.se.to_double(), -0.125, 0.001); + + TESTEQUALS_FLOAT(neighbors[1]->position.ne.to_double(), 0.125, 0.001); + TESTEQUALS_FLOAT(neighbors[1]->position.se.to_double(), 0, 0.001); + + TESTEQUALS_FLOAT(neighbors[2]->position.ne.to_double(), 0.125, 0.001); + TESTEQUALS_FLOAT(neighbors[2]->position.se.to_double(), 0.125, 0.001); + + TESTEQUALS_FLOAT(neighbors[3]->position.ne.to_double(), 0, 0.001); + TESTEQUALS_FLOAT(neighbors[3]->position.se.to_double(), 0.125, 0.001); + + TESTEQUALS_FLOAT(neighbors[4]->position.ne.to_double(), -0.125, 0.001); + TESTEQUALS_FLOAT(neighbors[4]->position.se.to_double(), 0.125, 0.001); + + TESTEQUALS_FLOAT(neighbors[5]->position.ne.to_double(), -0.125, 0.001); + TESTEQUALS_FLOAT(neighbors[5]->position.se.to_double(), 0, 0.001); + + TESTEQUALS_FLOAT(neighbors[6]->position.ne.to_double(), -0.125, 0.001); + TESTEQUALS_FLOAT(neighbors[6]->position.se.to_double(), -0.125, 0.001); + + TESTEQUALS_FLOAT(neighbors[7]->position.ne.to_double(), 0, 0.001); + TESTEQUALS_FLOAT(neighbors[7]->position.se.to_double(), -0.125, 0.001); + + // Testing how a larger scale changes the neighbors generated + neighbors = n0->get_neighbors(map, 2); + TESTEQUALS(neighbors.size(), 8); + + TESTEQUALS_FLOAT(neighbors[0]->position.ne.to_double(), 0.25, 0.001); + TESTEQUALS_FLOAT(neighbors[0]->position.se.to_double(), -0.25, 0.001); + + TESTEQUALS_FLOAT(neighbors[1]->position.ne.to_double(), 0.25, 0.001); + TESTEQUALS_FLOAT(neighbors[1]->position.se.to_double(), 0, 0.001); + + TESTEQUALS_FLOAT(neighbors[2]->position.ne.to_double(), 0.25, 0.001); + TESTEQUALS_FLOAT(neighbors[2]->position.se.to_double(), 0.25, 0.001); + + TESTEQUALS_FLOAT(neighbors[3]->position.ne.to_double(), 0, 0.001); + TESTEQUALS_FLOAT(neighbors[3]->position.se.to_double(), 0.25, 0.001); + + TESTEQUALS_FLOAT(neighbors[4]->position.ne.to_double(), -0.25, 0.001); + TESTEQUALS_FLOAT(neighbors[4]->position.se.to_double(), 0.25, 0.001); + + TESTEQUALS_FLOAT(neighbors[5]->position.ne.to_double(), -0.25, 0.001); + TESTEQUALS_FLOAT(neighbors[5]->position.se.to_double(), 0, 0.001); + + TESTEQUALS_FLOAT(neighbors[6]->position.ne.to_double(), -0.25, 0.001); + TESTEQUALS_FLOAT(neighbors[6]->position.se.to_double(), -0.25, 0.001); + + TESTEQUALS_FLOAT(neighbors[7]->position.ne.to_double(), 0, 0.001); + TESTEQUALS_FLOAT(neighbors[7]->position.se.to_double(), -0.25, 0.001); +} + +/** + * This is a helper passable function that alwalys returns true. + */ +bool always_passable(const coord::phys3 &) { + return true; +} + +/** + * This is a helper passable function that always returns false. + */ +bool not_passable(const coord::phys3 &) { + return false; +} + +/** + * This is a helper passable function that only returns true when + * pos.ne == 20. + */ +bool sometimes_passable(const coord::phys3 &pos) { + if (pos.ne == 20) { + return false; + } + else { + return true; + } +} + +/** + * This function tests passable_line. Tests with always false, always true, + * and position dependant functions being passed in as args. + */ +void node_passable_line_0() { + coord::phys3 p0{0, 0, 0}; + coord::phys3 p1{1000, 0, 0}; + + node_pt n0 = std::make_unique(p0, nullptr); + node_pt n1 = std::make_unique(p1, n0); + + TESTEQUALS(path::passable_line(n0, n1, path::tests::always_passable), true); + TESTEQUALS(path::passable_line(n0, n1, path::tests::not_passable), false); + + // The next 2 cases show that a different sample can change the results + // for the same path + TESTEQUALS(path::passable_line(n0, n1, path::tests::sometimes_passable, 10), true); + TESTEQUALS(path::passable_line(n0, n1, path::tests::sometimes_passable, 50), false); +} + +/** + * Top level node test. + */ +void path_node() { + node_0(); + node_cost_to_0(); + node_cost_to_1(); + node_generate_backtrace_0(); + node_get_neighbors_0(); + node_passable_line_0(); +} + +} // namespace tests +} // namespace path +} // namespace openage diff --git a/libopenage/pathfinding/tests.cpp b/libopenage/pathfinding/tests.cpp index 4cd6d40363..e31e87498e 100644 --- a/libopenage/pathfinding/tests.cpp +++ b/libopenage/pathfinding/tests.cpp @@ -1,323 +1,19 @@ // Copyright 2015-2024 the openage authors. See copying.md for legal info. -#include "../log/log.h" -#include "../testing/testing.h" +#include "log/log.h" +#include "testing/testing.h" -#include "cost_field.h" -#include "definitions.h" -#include "flow_field.h" -#include "heuristics.h" -#include "integration_field.h" -#include "integrator.h" -#include "path.h" -#include "types.h" +#include "pathfinding/cost_field.h" +#include "pathfinding/definitions.h" +#include "pathfinding/flow_field.h" +#include "pathfinding/integration_field.h" +#include "pathfinding/integrator.h" +#include "pathfinding/types.h" namespace openage { namespace path { namespace tests { -/** - * This function tests setting up basic nodes that point to a previous node. - * Tests that direction is set correctly and that factor is set correctly. - */ -void node_0() { - coord::phys3 p0{0, 0, 0}; - coord::phys3 p1{1, 0, 0}; - coord::phys3 p2{1, 1, 0}; - coord::phys3 p3{1, -1, 0}; - coord::phys3 p4{2, 0, 0}; - coord::phys3 p5{2, 2, 0}; - coord::phys3 p6{2, -2, 0}; - - node_pt n0 = std::make_unique(p0, nullptr); - node_pt n1 = std::make_unique(p1, n0); - node_pt n2 = std::make_unique(p2, n1); - node_pt n3 = std::make_unique(p3, n1); - node_pt n4 = std::make_unique(p0, n1); - - // Testing how the factor is effected from the change in - // direction from one node to another - TESTEQUALS(n1->direction.ne, 1); - TESTEQUALS(n1->direction.se, 0); - - // Expect this to be 2 since the similarity between nodes is zero - TESTEQUALS(n1->factor, 2); - - TESTEQUALS(n2->direction.ne, 0); - TESTEQUALS(n2->direction.se, 1); - - // Expect this to be 2 since it takes a 90 degree turn from n1 - TESTEQUALS(n2->factor, 2); - - TESTEQUALS(n3->direction.ne, 0); - TESTEQUALS(n3->direction.se, -1); - - // Expect this to be 2 since it takes a 90 degree turn from n1 - TESTEQUALS(n3->factor, 2); - - TESTEQUALS(n4->direction.ne, -1); - TESTEQUALS(n4->direction.se, 0); - - // Expect this to be 3 since it takes a 180 degree turn from n1 - TESTEQUALS(n4->factor, 3); - - // Testing that the distance from the previous node noes not - // effect the factor, only change in direction - - n1 = std::make_unique(p4, n0); - n2 = std::make_unique(p5, n1); - n3 = std::make_unique(p6, n1); - n4 = std::make_unique(p0, n1); - - TESTEQUALS(n1->direction.ne, 1); - TESTEQUALS(n1->direction.se, 0); - - // Expect this to be 2 since the similarity between nodes is zero - TESTEQUALS(n1->factor, 2); - TESTEQUALS(n2->direction.ne, 0); - TESTEQUALS(n2->direction.se, 1); - - // Expect this to be 2 since it takes a 90 degree turn from n1 - TESTEQUALS(n2->factor, 2); - TESTEQUALS(n3->direction.ne, 0); - TESTEQUALS(n3->direction.se, -1); - - // Expect this to be 2 since it takes a 90 degree turn from n1 - TESTEQUALS(n3->factor, 2); - TESTEQUALS(n4->direction.ne, -1); - TESTEQUALS(n4->direction.se, 0); - - // Expect this to be 3 since it takes a 180 degree turn from n1 - TESTEQUALS(n4->factor, 3); -} - -/** - * This function tests Node->cost_to. The testing is done on 2 unrelated - * nodes (They have no previous node) to test the basic cost without adding - * the cost from node->factor. - */ -void node_cost_to_0() { - // Testing basic cost_to with ne only - coord::phys3 p0{0, 0, 0}; - coord::phys3 p1{10, 0, 0}; - - node_pt n0 = std::make_unique(p0, nullptr); - node_pt n1 = std::make_unique(p1, nullptr); - - TESTEQUALS(n0->cost_to(*n1), 10); - TESTEQUALS(n1->cost_to(*n0), 10); - - // Testing basic cost_to with se only - coord::phys3 p2{0, 5, 0}; - - node_pt n2 = std::make_unique(p2, nullptr); - - TESTEQUALS(n0->cost_to(*n2), 5); - TESTEQUALS(n2->cost_to(*n0), 5); - - // Testing cost_to with both se and ne: - coord::phys3 p3{3, 4, 0}; // -> sqrt(3*3 + 4*4) == 5 - - node_pt n3 = std::make_unique(p3, nullptr); - TESTEQUALS(n0->cost_to(*n3), 5); - TESTEQUALS(n3->cost_to(*n0), 5); - - // Test cost_to and check that `up` has no effect - coord::phys3 p4{3, 4, 8}; - - node_pt n4 = std::make_unique(p4, nullptr); - - TESTEQUALS(n0->cost_to(*n4), 5); - TESTEQUALS(n4->cost_to(*n0), 5); -} - -/** - * This function tests Node->cost_to. The testing is done on the neighbor - * nodes to test how the directional factor effects the cost. - */ -void node_cost_to_1() { - // Set up coords so that n1 will have a direction of ne = 1 - // but n0 with not be in n1s neighbors - coord::phys3 p0{-0.125, 0, 0}; - coord::phys3 p1{0.125, 0, 0}; - - node_pt n0 = std::make_unique(p0, nullptr); - node_pt n1 = std::make_unique(p1, n0); - - // We expect twice the normal cost since n0 had not direction - // thus we get a factor of 2 on n1 - TESTEQUALS_FLOAT(n0->cost_to(*n1), 0.5, 0.001); - TESTEQUALS_FLOAT(n1->cost_to(*n0), 0.5, 0.001); - - nodemap_t visited_tiles; - visited_tiles[n0->position] = n0; - - // Collect the costs to go to all the neighbors of n1 - std::vector costs; - for (node_pt neighbor : n1->get_neighbors(visited_tiles, 1)) { - costs.push_back(n1->cost_to(*neighbor)); - } - - TESTEQUALS_FLOAT(costs[0], 0.45711, 0.001); - TESTEQUALS_FLOAT(costs[1], 0.25, 0.001); - TESTEQUALS_FLOAT(costs[2], 0.45711, 0.001); - TESTEQUALS_FLOAT(costs[3], 0.5, 0.001); - TESTEQUALS_FLOAT(costs[4], 0.95709, 0.001); - TESTEQUALS_FLOAT(costs[5], 0.75, 0.001); - TESTEQUALS_FLOAT(costs[6], 0.95709, 0.001); - TESTEQUALS_FLOAT(costs[7], 0.5, 0.001); -} - -/** - * This function does a basic test of generating a backtrace from the - * last node in a path. - */ -void node_generate_backtrace_0() { - coord::phys3 p0{0, 0, 0}; - coord::phys3 p1{10, 0, 0}; - coord::phys3 p2{20, 0, 0}; - coord::phys3 p3{30, 0, 0}; - - node_pt n0 = std::make_unique(p0, nullptr); - node_pt n1 = std::make_unique(p1, n0); - node_pt n2 = std::make_unique(p2, n1); - node_pt n3 = std::make_unique(p3, n2); - - Path path = n3->generate_backtrace(); - - (path.waypoints[0] == *n3) or TESTFAIL; - (path.waypoints[1] == *n2) or TESTFAIL; - (path.waypoints[2] == *n1) or TESTFAIL; -} - -/** - * This function tests Node->get_neighbors and how the scale effects - * the neighbors given. - */ -void node_get_neighbors_0() { - coord::phys3 p0{0, 0, 0}; - - node_pt n0 = std::make_unique(p0, nullptr); - nodemap_t map; - - // Testing get_neighbors returning all surounding tiles with - // a factor of 1 - - std::vector neighbors = n0->get_neighbors(map, 1); - TESTEQUALS(neighbors.size(), 8); - - TESTEQUALS_FLOAT(neighbors[0]->position.ne.to_double(), 0.125, 0.001); - TESTEQUALS_FLOAT(neighbors[0]->position.se.to_double(), -0.125, 0.001); - - TESTEQUALS_FLOAT(neighbors[1]->position.ne.to_double(), 0.125, 0.001); - TESTEQUALS_FLOAT(neighbors[1]->position.se.to_double(), 0, 0.001); - - TESTEQUALS_FLOAT(neighbors[2]->position.ne.to_double(), 0.125, 0.001); - TESTEQUALS_FLOAT(neighbors[2]->position.se.to_double(), 0.125, 0.001); - - TESTEQUALS_FLOAT(neighbors[3]->position.ne.to_double(), 0, 0.001); - TESTEQUALS_FLOAT(neighbors[3]->position.se.to_double(), 0.125, 0.001); - - TESTEQUALS_FLOAT(neighbors[4]->position.ne.to_double(), -0.125, 0.001); - TESTEQUALS_FLOAT(neighbors[4]->position.se.to_double(), 0.125, 0.001); - - TESTEQUALS_FLOAT(neighbors[5]->position.ne.to_double(), -0.125, 0.001); - TESTEQUALS_FLOAT(neighbors[5]->position.se.to_double(), 0, 0.001); - - TESTEQUALS_FLOAT(neighbors[6]->position.ne.to_double(), -0.125, 0.001); - TESTEQUALS_FLOAT(neighbors[6]->position.se.to_double(), -0.125, 0.001); - - TESTEQUALS_FLOAT(neighbors[7]->position.ne.to_double(), 0, 0.001); - TESTEQUALS_FLOAT(neighbors[7]->position.se.to_double(), -0.125, 0.001); - - // Testing how a larger scale changes the neighbors generated - neighbors = n0->get_neighbors(map, 2); - TESTEQUALS(neighbors.size(), 8); - - TESTEQUALS_FLOAT(neighbors[0]->position.ne.to_double(), 0.25, 0.001); - TESTEQUALS_FLOAT(neighbors[0]->position.se.to_double(), -0.25, 0.001); - - TESTEQUALS_FLOAT(neighbors[1]->position.ne.to_double(), 0.25, 0.001); - TESTEQUALS_FLOAT(neighbors[1]->position.se.to_double(), 0, 0.001); - - TESTEQUALS_FLOAT(neighbors[2]->position.ne.to_double(), 0.25, 0.001); - TESTEQUALS_FLOAT(neighbors[2]->position.se.to_double(), 0.25, 0.001); - - TESTEQUALS_FLOAT(neighbors[3]->position.ne.to_double(), 0, 0.001); - TESTEQUALS_FLOAT(neighbors[3]->position.se.to_double(), 0.25, 0.001); - - TESTEQUALS_FLOAT(neighbors[4]->position.ne.to_double(), -0.25, 0.001); - TESTEQUALS_FLOAT(neighbors[4]->position.se.to_double(), 0.25, 0.001); - - TESTEQUALS_FLOAT(neighbors[5]->position.ne.to_double(), -0.25, 0.001); - TESTEQUALS_FLOAT(neighbors[5]->position.se.to_double(), 0, 0.001); - - TESTEQUALS_FLOAT(neighbors[6]->position.ne.to_double(), -0.25, 0.001); - TESTEQUALS_FLOAT(neighbors[6]->position.se.to_double(), -0.25, 0.001); - - TESTEQUALS_FLOAT(neighbors[7]->position.ne.to_double(), 0, 0.001); - TESTEQUALS_FLOAT(neighbors[7]->position.se.to_double(), -0.25, 0.001); -} - -/** - * This is a helper passable function that alwalys returns true. - */ -bool always_passable(const coord::phys3 &) { - return true; -} - -/** - * This is a helper passable function that always returns false. - */ -bool not_passable(const coord::phys3 &) { - return false; -} - -/** - * This is a helper passable function that only returns true when - * pos.ne == 20. - */ -bool sometimes_passable(const coord::phys3 &pos) { - if (pos.ne == 20) { - return false; - } - else { - return true; - } -} - -/** - * This function tests passable_line. Tests with always false, always true, - * and position dependant functions being passed in as args. - */ -void node_passable_line_0() { - coord::phys3 p0{0, 0, 0}; - coord::phys3 p1{1000, 0, 0}; - - node_pt n0 = std::make_unique(p0, nullptr); - node_pt n1 = std::make_unique(p1, n0); - - TESTEQUALS(path::passable_line(n0, n1, path::tests::always_passable), true); - TESTEQUALS(path::passable_line(n0, n1, path::tests::not_passable), false); - - // The next 2 cases show that a different sample can change the results - // for the same path - TESTEQUALS(path::passable_line(n0, n1, path::tests::sometimes_passable, 10), true); - TESTEQUALS(path::passable_line(n0, n1, path::tests::sometimes_passable, 50), false); -} - -/** - * Top level node test. - */ -void path_node() { - node_0(); - node_cost_to_0(); - node_cost_to_1(); - node_generate_backtrace_0(); - node_get_neighbors_0(); - node_passable_line_0(); -} - void flow_field() { // Create initial cost grid auto cost_field = std::make_shared(3); From d7424b38bba2352c377712d5d52077f92bef5f4c Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 17 Mar 2024 21:09:05 +0100 Subject: [PATCH 053/112] path: Pathfinder object. --- libopenage/pathfinding/CMakeLists.txt | 1 + libopenage/pathfinding/pathfinder.cpp | 19 +++++++++ libopenage/pathfinding/pathfinder.h | 55 +++++++++++++++++++++++++++ 3 files changed, 75 insertions(+) create mode 100644 libopenage/pathfinding/pathfinder.cpp create mode 100644 libopenage/pathfinding/pathfinder.h diff --git a/libopenage/pathfinding/CMakeLists.txt b/libopenage/pathfinding/CMakeLists.txt index 84e7374203..ed55ffc463 100644 --- a/libopenage/pathfinding/CMakeLists.txt +++ b/libopenage/pathfinding/CMakeLists.txt @@ -5,6 +5,7 @@ add_sources(libopenage grid.cpp integration_field.cpp integrator.cpp + pathfinder.cpp portal.cpp sector.cpp tests.cpp diff --git a/libopenage/pathfinding/pathfinder.cpp b/libopenage/pathfinding/pathfinder.cpp new file mode 100644 index 0000000000..7b3378f91e --- /dev/null +++ b/libopenage/pathfinding/pathfinder.cpp @@ -0,0 +1,19 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#include "pathfinder.h" + +#include "pathfinding/integrator.h" + + +namespace openage::path { + +Pathfinder::Pathfinder() : + grids{}, + integrator{std::make_shared()} { +} + +const std::shared_ptr &Pathfinder::get_grid(size_t idx) const { + return this->grids.at(idx); +} + +} // namespace openage::path diff --git a/libopenage/pathfinding/pathfinder.h b/libopenage/pathfinding/pathfinder.h new file mode 100644 index 0000000000..e93b5fccf1 --- /dev/null +++ b/libopenage/pathfinding/pathfinder.h @@ -0,0 +1,55 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#pragma once + +#include +#include + + +namespace openage::path { +class Grid; +class Integrator; + +/** + * Pathfinder for flow field pathfinding. + * + * The pathfinder manages the grids defining the pathable ingame areas and + * provides an interface for making pathfinding requests. + * + * Pathfinding consists of a multi-step process: First, there is a high-level + * search using A* to identify the sectors of the grid that should be traversed. + * Afterwards, flow fields are calculated from the target sector to the start + * sector, which are then used to guide the actual unit movement. + */ +class Pathfinder { +public: + /** + * Create a new pathfinder. + */ + Pathfinder(); + ~Pathfinder() = default; + + /** + * Get the grid at a specified index. + * + * @param idx Index of the grid. + * + * @return Pathfinding grid. + */ + const std::shared_ptr &get_grid(size_t idx) const; + +private: + /** + * Grids managed by this pathfinder. + * + * Each grid can have separate pathing. + */ + std::unordered_map> grids; + + /** + * Integrator for flow field calculations. + */ + std::shared_ptr integrator; +}; + +} // namespace openage::path From b46c9fad20eaa9b72e5bca098fd2bc57e90029c7 Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 18 Mar 2024 00:29:18 +0100 Subject: [PATCH 054/112] path: Path request class. --- libopenage/pathfinding/CMakeLists.txt | 1 + libopenage/pathfinding/path.cpp | 10 ++++++++ libopenage/pathfinding/path.h | 34 +++++++++++++++++++++++++++ 3 files changed, 45 insertions(+) create mode 100644 libopenage/pathfinding/path.cpp create mode 100644 libopenage/pathfinding/path.h diff --git a/libopenage/pathfinding/CMakeLists.txt b/libopenage/pathfinding/CMakeLists.txt index ed55ffc463..7f85264d01 100644 --- a/libopenage/pathfinding/CMakeLists.txt +++ b/libopenage/pathfinding/CMakeLists.txt @@ -5,6 +5,7 @@ add_sources(libopenage grid.cpp integration_field.cpp integrator.cpp + path.cpp pathfinder.cpp portal.cpp sector.cpp diff --git a/libopenage/pathfinding/path.cpp b/libopenage/pathfinding/path.cpp new file mode 100644 index 0000000000..fe13989e77 --- /dev/null +++ b/libopenage/pathfinding/path.cpp @@ -0,0 +1,10 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#include "path.h" + + +namespace openage::path { + +// this file is intentionally empty + +} // namespace openage::path diff --git a/libopenage/pathfinding/path.h b/libopenage/pathfinding/path.h new file mode 100644 index 0000000000..cb08e09991 --- /dev/null +++ b/libopenage/pathfinding/path.h @@ -0,0 +1,34 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#pragma once + +#include + +#include "coord/tile.h" + + +namespace openage::path { + +/** + * Path request for the pathfinder. + */ +struct PathRequest { + // ID of the grid to use for pathfinding. + size_t grid_id; + // Start position of the path. + coord::tile start; + // Target position of the path. + coord::tile target; +}; + +/** + * Path found by the pathfinder. + */ +struct Path { + // ID of the grid to used for pathfinding. + size_t grid_id; + // Waypoints of the path. + std::vector waypoints; +}; + +} // namespace openage::path From 2f589870dee957a8b35f6eb96b26f357dca4ae03 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sat, 23 Mar 2024 12:05:16 +0100 Subject: [PATCH 055/112] path: Do not render vectors for LOS cells in demo. --- libopenage/pathfinding/demo/demo_0.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libopenage/pathfinding/demo/demo_0.cpp b/libopenage/pathfinding/demo/demo_0.cpp index e655598979..0de6d13fc3 100644 --- a/libopenage/pathfinding/demo/demo_0.cpp +++ b/libopenage/pathfinding/demo/demo_0.cpp @@ -267,7 +267,7 @@ void RenderManager::show_vectors(const std::shared_ptr &field) for (size_t y = 0; y < field->get_size(); ++y) { for (size_t x = 0; x < field->get_size(); ++x) { auto cell = field->get_cell(x, y); - if (cell & FLOW_PATHABLE_MASK) { + if (cell & FLOW_PATHABLE_MASK and not(cell & FLOW_LOS_MASK)) { Eigen::Affine3f arrow_model = Eigen::Affine3f::Identity(); arrow_model.translate(Eigen::Vector3f(y + 0.5, 0, -1.0f * x - 0.5)); auto dir = static_cast(cell & FLOW_DIR_MASK); From 4170ff1d5a13ba72b62f2d2a6620cb804d505a83 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sat, 23 Mar 2024 12:44:31 +0100 Subject: [PATCH 056/112] path: Assign IDs to grids and portals. --- libopenage/pathfinding/grid.cpp | 16 ++++++++++++++-- libopenage/pathfinding/grid.h | 20 ++++++++++++++++++-- libopenage/pathfinding/pathfinder.cpp | 4 ++-- libopenage/pathfinding/pathfinder.h | 4 ++-- libopenage/pathfinding/portal.cpp | 12 +++++++++++- libopenage/pathfinding/portal.h | 25 ++++++++++++++++++++++++- libopenage/pathfinding/sector.cpp | 7 ++++++- libopenage/pathfinding/sector.h | 14 +++++++++----- libopenage/pathfinding/types.h | 12 +++++++++++- 9 files changed, 97 insertions(+), 17 deletions(-) diff --git a/libopenage/pathfinding/grid.cpp b/libopenage/pathfinding/grid.cpp index 86b71d8681..6ad53178f0 100644 --- a/libopenage/pathfinding/grid.cpp +++ b/libopenage/pathfinding/grid.cpp @@ -2,12 +2,15 @@ #include "grid.h" +#include "error/error.h" + #include "pathfinding/sector.h" namespace openage::path { -Grid::Grid(size_t width, size_t height, size_t sector_size) : +Grid::Grid(grid_id_t id, size_t width, size_t height, size_t sector_size) : + id{id}, width{width}, height{height} { for (size_t y = 0; y < height; y++) { @@ -17,12 +20,21 @@ Grid::Grid(size_t width, size_t height, size_t sector_size) : } } -Grid::Grid(size_t width, +Grid::Grid(grid_id_t id, + size_t width, size_t height, std::vector> &§ors) : + id{id}, width{width}, height{height}, sectors{std::move(sectors)} { + ENSURE(this->sectors.size() == width * height, + "Grid has size " << width << "x" << height << " (" << width * height << " sectors), " + << "but only " << this->sectors.size() << " sectors were provided"); +} + +grid_id_t Grid::get_id() const { + return this->id; } std::pair Grid::get_size() const { diff --git a/libopenage/pathfinding/grid.h b/libopenage/pathfinding/grid.h index 22c551066c..adf030cf53 100644 --- a/libopenage/pathfinding/grid.h +++ b/libopenage/pathfinding/grid.h @@ -21,25 +21,36 @@ class Grid { /** * Create a new empty grid of width x height sectors with a specified size. * + * @param id ID of the grid. * @param width Width of the grid. * @param height Height of the grid. * @param sector_size Side length of each sector. */ - Grid(size_t width, + Grid(grid_id_t id, + 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 id ID of the grid. * @param width Width of the grid. * @param height Height of the grid. * @param sectors Existing sectors. */ - Grid(size_t width, + Grid(grid_id_t id, + size_t width, size_t height, std::vector> &§ors); + /** + * Get the ID of the grid. + * + * @return ID of the grid. + */ + grid_id_t get_id() const; + /** * Get the size of the grid. * @@ -67,6 +78,11 @@ class Grid { const std::shared_ptr &get_sector(sector_id_t id) const; private: + /** + * ID of the grid. + */ + grid_id_t id; + /** * Width of the grid. */ diff --git a/libopenage/pathfinding/pathfinder.cpp b/libopenage/pathfinding/pathfinder.cpp index 7b3378f91e..44be92ccd1 100644 --- a/libopenage/pathfinding/pathfinder.cpp +++ b/libopenage/pathfinding/pathfinder.cpp @@ -12,8 +12,8 @@ Pathfinder::Pathfinder() : integrator{std::make_shared()} { } -const std::shared_ptr &Pathfinder::get_grid(size_t idx) const { - return this->grids.at(idx); +const std::shared_ptr &Pathfinder::get_grid(size_t id) const { + return this->grids.at(id); } } // namespace openage::path diff --git a/libopenage/pathfinding/pathfinder.h b/libopenage/pathfinding/pathfinder.h index e93b5fccf1..d423272ff1 100644 --- a/libopenage/pathfinding/pathfinder.h +++ b/libopenage/pathfinding/pathfinder.h @@ -32,11 +32,11 @@ class Pathfinder { /** * Get the grid at a specified index. * - * @param idx Index of the grid. + * @param id ID of the grid. * * @return Pathfinding grid. */ - const std::shared_ptr &get_grid(size_t idx) const; + const std::shared_ptr &get_grid(size_t id) const; private: /** diff --git a/libopenage/pathfinding/portal.cpp b/libopenage/pathfinding/portal.cpp index 40111e0b98..0b3b472667 100644 --- a/libopenage/pathfinding/portal.cpp +++ b/libopenage/pathfinding/portal.cpp @@ -7,13 +7,15 @@ namespace openage::path { -Portal::Portal(sector_id_t sector0, +Portal::Portal(portal_id_t id, + sector_id_t sector0, sector_id_t sector1, PortalDirection direction, size_t cell_start_x, size_t cell_start_y, size_t cell_end_x, size_t cell_end_y) : + id{id}, sector0{sector0}, sector1{sector1}, sector0_exits{}, @@ -25,6 +27,10 @@ Portal::Portal(sector_id_t sector0, cell_end_y{cell_end_y} { } +portal_id_t Portal::get_id() const { + return this->id; +} + const std::vector> &Portal::get_exits(sector_id_t entry_sector) const { ENSURE(entry_sector == this->sector0 || entry_sector == this->sector1, "Invalid entry sector"); @@ -94,6 +100,10 @@ std::pair Portal::get_exit_end(sector_id_t entry_sector) const { return this->get_sector0_end(); } +PortalDirection Portal::get_direction() const { + return this->direction; +} + std::pair Portal::get_sector0_start() const { return {this->cell_start_x, this->cell_start_y}; } diff --git a/libopenage/pathfinding/portal.h b/libopenage/pathfinding/portal.h index dd6e474931..170fd463ca 100644 --- a/libopenage/pathfinding/portal.h +++ b/libopenage/pathfinding/portal.h @@ -42,6 +42,7 @@ class Portal { * As a convention, sector 0 must be the either the north or east sector * on the grid in relation to sector 1. * + * @param id ID of the portal. Should be unique per grid. * @param sector0 First sector connected by the portal. * Must be north or east on the grid in relation to sector 1. * @param sector1 Second sector connected by the portal. @@ -52,7 +53,8 @@ class Portal { * @param cell_end_x End cell x coordinate in sector 0. * @param cell_end_y End cell y coordinate in sector 0. */ - Portal(sector_id_t sector0, + Portal(portal_id_t id, + sector_id_t sector0, sector_id_t sector1, PortalDirection direction, size_t cell_start_x, @@ -62,6 +64,15 @@ class Portal { ~Portal() = default; + /** + * Get the ID of the portal. + * + * IDs are unique per grid. + * + * @return ID of the portal. + */ + portal_id_t get_id() const; + /** * Get the exit portals reachable via the portal when entering from a specified sector. * @@ -124,6 +135,13 @@ class Portal { */ std::pair get_exit_end(sector_id_t entry_sector) const; + /** + * Get the direction of the portal from sector 0 to sector 1. + * + * @return Direction of the portal. + */ + PortalDirection get_direction() const; + private: /** * Get the start cell coordinates of the portal. @@ -153,6 +171,11 @@ class Portal { */ std::pair get_sector1_end() const; + /** + * ID of the portal. + */ + portal_id_t id; + /** * First sector connected by the portal. */ diff --git a/libopenage/pathfinding/sector.cpp b/libopenage/pathfinding/sector.cpp index 8c5221821d..554a5518b1 100644 --- a/libopenage/pathfinding/sector.cpp +++ b/libopenage/pathfinding/sector.cpp @@ -37,7 +37,8 @@ void Sector::add_portal(const std::shared_ptr &portal) { } std::vector> Sector::find_portals(const std::shared_ptr &other, - PortalDirection direction) const { + PortalDirection direction, + portal_id_t next_id) const { ENSURE(this->cost_field->get_size() == other->get_cost_field()->get_size(), "Sector size mismatch"); std::vector> result; @@ -62,6 +63,7 @@ std::vector> Sector::find_portals(const std::shared_ptr< if (passable_edge) { result.push_back( std::make_shared( + next_id, this->id, other->get_id(), direction, @@ -70,6 +72,7 @@ std::vector> Sector::find_portals(const std::shared_ptr< x - 1, 0)); passable_edge = false; + next_id += 1; } } } @@ -90,6 +93,7 @@ std::vector> Sector::find_portals(const std::shared_ptr< if (passable_edge) { result.push_back( std::make_shared( + next_id, this->id, other->get_id(), direction, @@ -98,6 +102,7 @@ std::vector> Sector::find_portals(const std::shared_ptr< 0, y - 1)); passable_edge = false; + next_id += 1; } } } diff --git a/libopenage/pathfinding/sector.h b/libopenage/pathfinding/sector.h index b0dbd2bc18..66c2090ec9 100644 --- a/libopenage/pathfinding/sector.h +++ b/libopenage/pathfinding/sector.h @@ -25,7 +25,7 @@ class Sector { /** * Create a new sector with a specified ID and an uninitialized cost field. * - * @param id ID of the sector. + * @param id ID of the sector. Should be unique per grid. * @param field_size Size of the cost field. */ Sector(sector_id_t id, @@ -34,7 +34,7 @@ class Sector { /** * Create a new sector with a specified ID and an existing cost field. * - * @param id ID of the sector. + * @param id ID of the sector. Should be unique per grid. * @param cost_field Cost field of the sector. */ Sector(sector_id_t id, @@ -43,6 +43,8 @@ class Sector { /** * Get the ID of this sector. * + * IDs are unique per grid. + * * @return ID of the sector. */ const sector_id_t &get_id() const; @@ -72,12 +74,14 @@ class Sector { * 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. + * @param direction Direction from this sector to \p other sector. + * @param next_id ID of the next portal to be created. Should be unique per grid. * - * @return Portals connecting this sector to the other sector. + * @return Portals connecting this sector to \p other sector. */ std::vector> find_portals(const std::shared_ptr &other, - PortalDirection direction) const; + PortalDirection direction, + portal_id_t next_id) const; /** * Connect all portals that are mutually reachable. diff --git a/libopenage/pathfinding/types.h b/libopenage/pathfinding/types.h index 118c8c116d..6ed735e896 100644 --- a/libopenage/pathfinding/types.h +++ b/libopenage/pathfinding/types.h @@ -57,8 +57,18 @@ struct integrate_t { using flow_t = uint8_t; /** - * Sector identifier on a grid. + * Grid identifier. + */ +using grid_id_t = size_t; + +/** + * Sector identifier (unique per grid). */ using sector_id_t = size_t; +/** + * Portal identifier (unique per grid). + */ +using portal_id_t = size_t; + } // namespace openage::path From 73d80628003cdabf8b6a7f76a2c6d13434941ee9 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sat, 23 Mar 2024 13:45:10 +0100 Subject: [PATCH 057/112] path: Port flowfield pathfinder to coordinate system. --- libopenage/coord/tile.h | 3 - libopenage/pathfinding/cost_field.cpp | 13 ++-- libopenage/pathfinding/cost_field.h | 28 +++++--- libopenage/pathfinding/demo/demo_0.cpp | 49 ++++++------- libopenage/pathfinding/flow_field.cpp | 10 +-- libopenage/pathfinding/flow_field.h | 20 +++--- libopenage/pathfinding/integration_field.cpp | 74 +++++++++----------- libopenage/pathfinding/integration_field.h | 49 ++++++------- libopenage/pathfinding/integrator.cpp | 4 +- libopenage/pathfinding/integrator.h | 15 ++-- libopenage/pathfinding/portal.cpp | 42 +++++------ libopenage/pathfinding/portal.h | 47 +++++-------- libopenage/pathfinding/sector.cpp | 21 +++--- libopenage/pathfinding/tests.cpp | 6 +- 14 files changed, 194 insertions(+), 187 deletions(-) diff --git a/libopenage/coord/tile.h b/libopenage/coord/tile.h index 5cb3f2475d..cf020823a7 100644 --- a/libopenage/coord/tile.h +++ b/libopenage/coord/tile.h @@ -9,9 +9,6 @@ #include "declarations.h" namespace openage { - -class Terrain; - namespace coord { /* diff --git a/libopenage/pathfinding/cost_field.cpp b/libopenage/pathfinding/cost_field.cpp index ad19f8b910..248a260d8e 100644 --- a/libopenage/pathfinding/cost_field.cpp +++ b/libopenage/pathfinding/cost_field.cpp @@ -5,6 +5,7 @@ #include "error/error.h" #include "log/log.h" +#include "coord/tile.h" #include "pathfinding/definitions.h" @@ -20,16 +21,20 @@ size_t CostField::get_size() const { return this->size; } -cost_t CostField::get_cost(size_t x, size_t y) const { - return this->cells[x + y * this->size]; +cost_t CostField::get_cost(const coord::tile &pos) const { + return this->cells.at(pos.ne + pos.se * this->size); } cost_t CostField::get_cost(size_t idx) const { return this->cells.at(idx); } -void CostField::set_cost(size_t x, size_t y, cost_t cost) { - this->cells[x + y * this->size] = cost; +void CostField::set_cost(const coord::tile &pos, cost_t cost) { + this->cells[pos.ne + pos.se * this->size] = cost; +} + +void CostField::set_cost(size_t idx, cost_t cost) { + this->cells[idx] = cost; } const std::vector &CostField::get_costs() const { diff --git a/libopenage/pathfinding/cost_field.h b/libopenage/pathfinding/cost_field.h index b4f5cfbb00..ffb27bdec8 100644 --- a/libopenage/pathfinding/cost_field.h +++ b/libopenage/pathfinding/cost_field.h @@ -8,7 +8,12 @@ #include "pathfinding/types.h" -namespace openage::path { +namespace openage { +namespace coord { +struct tile; +} // namespace coord + +namespace path { /** * Cost field in the flow-field pathfinding algorithm. @@ -32,11 +37,10 @@ class CostField { /** * Get the cost at a specified position. * - * @param x X coordinate. - * @param y Y coordinate. + * @param pos Coordinates of the cell. * @return Cost at the specified position. */ - cost_t get_cost(size_t x, size_t y) const; + cost_t get_cost(const coord::tile &pos) const; /** * Get the cost at a specified position. @@ -49,11 +53,18 @@ class CostField { /** * Set the cost at a specified position. * - * @param x X coordinate. - * @param y Y coordinate. + * @param pos Coordinates of the cell. + * @param cost Cost to set. + */ + void set_cost(const coord::tile &pos, cost_t cost); + + /** + * Set the cost at a specified position. + * + * @param idx Index of the cell. * @param cost Cost to set. */ - void set_cost(size_t x, size_t y, cost_t cost); + void set_cost(size_t idx, cost_t cost); /** * Get the cost field values. @@ -81,4 +92,5 @@ class CostField { std::vector cells; }; -} // namespace openage::path +} // namespace path +} // namespace openage diff --git a/libopenage/pathfinding/demo/demo_0.cpp b/libopenage/pathfinding/demo/demo_0.cpp index 0de6d13fc3..badbfef0d6 100644 --- a/libopenage/pathfinding/demo/demo_0.cpp +++ b/libopenage/pathfinding/demo/demo_0.cpp @@ -5,6 +5,7 @@ #include #include +#include "coord/tile.h" #include "pathfinding/cost_field.h" #include "pathfinding/flow_field.h" #include "pathfinding/integration_field.h" @@ -29,15 +30,15 @@ void path_demo_0(const util::Path &path) { // Cost field with some obstacles auto cost_field = std::make_shared(field_length); - cost_field->set_cost(0, 0, COST_IMPASSABLE); - cost_field->set_cost(1, 0, 254); - cost_field->set_cost(4, 3, 128); - cost_field->set_cost(5, 3, 128); - cost_field->set_cost(6, 3, 128); - cost_field->set_cost(4, 4, 128); - cost_field->set_cost(5, 4, 128); - cost_field->set_cost(6, 4, 128); - cost_field->set_cost(1, 7, COST_IMPASSABLE); + cost_field->set_cost(coord::tile{0, 0}, COST_IMPASSABLE); + cost_field->set_cost(coord::tile{1, 0}, 254); + cost_field->set_cost(coord::tile{4, 3}, 128); + cost_field->set_cost(coord::tile{5, 3}, 128); + cost_field->set_cost(coord::tile{6, 3}, 128); + cost_field->set_cost(coord::tile{4, 4}, 128); + cost_field->set_cost(coord::tile{5, 4}, 128); + cost_field->set_cost(coord::tile{6, 4}, 128); + cost_field->set_cost(coord::tile{1, 7}, COST_IMPASSABLE); log::log(INFO << "Created cost field"); // Create an integration field from the cost field @@ -45,7 +46,7 @@ void path_demo_0(const util::Path &path) { log::log(INFO << "Created integration field"); // Set cell (7, 7) to be the initial target cell - auto wavefront_blocked = integration_field->integrate_los(cost_field, 7, 7); + auto wavefront_blocked = integration_field->integrate_los(cost_field, coord::tile{7, 7}); integration_field->integrate_cost(cost_field, std::move(wavefront_blocked)); log::log(INFO << "Calculated integration field for target cell (7, 7)"); @@ -83,7 +84,7 @@ void path_demo_0(const util::Path &path) { // Recalculate the integration field and the flow field integration_field->reset(); - auto wavefront_blocked = integration_field->integrate_los(cost_field, grid_x, grid_y); + auto wavefront_blocked = integration_field->integrate_los(cost_field, coord::tile{grid_x, grid_y}); integration_field->integrate_cost(cost_field, std::move(wavefront_blocked)); log::log(INFO << "Calculated integration field for target cell (" << grid_x << ", " << grid_y << ")"); @@ -266,7 +267,7 @@ void RenderManager::show_vectors(const std::shared_ptr &field) this->vector_pass->clear_renderables(); for (size_t y = 0; y < field->get_size(); ++y) { for (size_t x = 0; x < field->get_size(); ++x) { - auto cell = field->get_cell(x, y); + auto cell = field->get_cell(coord::tile{x, y}); if (cell & FLOW_PATHABLE_MASK and not(cell & FLOW_LOS_MASK)) { Eigen::Affine3f arrow_model = Eigen::Affine3f::Identity(); arrow_model.translate(Eigen::Vector3f(y + 0.5, 0, -1.0f * x - 0.5)); @@ -557,19 +558,19 @@ renderer::resources::MeshData RenderManager::get_cost_field_mesh(const std::shar // for each vertex, compare the surrounding tiles std::vector surround{}; if (j - 1 >= 0 and i - 1 >= 0) { - auto cost = field->get_cost((i - 1) / resolution, (j - 1) / resolution); + auto cost = field->get_cost(coord::tile{(i - 1) / resolution, (j - 1) / resolution}); surround.push_back(cost); } if (j < static_cast(field->get_size()) and i - 1 >= 0) { - auto cost = field->get_cost((i - 1) / resolution, j / resolution); + auto cost = field->get_cost(coord::tile{(i - 1) / resolution, j / resolution}); surround.push_back(cost); } if (j < static_cast(field->get_size()) and i < static_cast(field->get_size())) { - auto cost = field->get_cost(i / resolution, j / resolution); + auto cost = field->get_cost(coord::tile{i / resolution, j / resolution}); surround.push_back(cost); } if (j - 1 >= 0 and i < static_cast(field->get_size())) { - auto cost = field->get_cost(i / resolution, (j - 1) / resolution); + auto cost = field->get_cost(coord::tile{i / resolution, (j - 1) / resolution}); surround.push_back(cost); } // use the cost of the most expensive surrounding tile @@ -643,19 +644,19 @@ renderer::resources::MeshData RenderManager::get_integration_field_mesh(const st // for each vertex, compare the surrounding tiles std::vector surround{}; if (j - 1 >= 0 and i - 1 >= 0) { - auto cost = field->get_cell((i - 1) / resolution, (j - 1) / resolution).cost; + auto cost = field->get_cell(coord::tile{(i - 1) / resolution, (j - 1) / resolution}).cost; surround.push_back(cost); } if (j < static_cast(field->get_size()) and i - 1 >= 0) { - auto cost = field->get_cell((i - 1) / resolution, j / resolution).cost; + auto cost = field->get_cell(coord::tile{(i - 1) / resolution, j / resolution}).cost; surround.push_back(cost); } if (j < static_cast(field->get_size()) and i < static_cast(field->get_size())) { - auto cost = field->get_cell(i / resolution, j / resolution).cost; + auto cost = field->get_cell(coord::tile{i / resolution, j / resolution}).cost; surround.push_back(cost); } if (j - 1 >= 0 and i < static_cast(field->get_size())) { - auto cost = field->get_cell(i / resolution, (j - 1) / resolution).cost; + auto cost = field->get_cell(coord::tile{i / resolution, (j - 1) / resolution}).cost; surround.push_back(cost); } // use the cost of the most expensive surrounding tile @@ -729,19 +730,19 @@ renderer::resources::MeshData RenderManager::get_flow_field_mesh(const std::shar // for each vertex, compare the surrounding tiles std::vector surround{}; if (j - 1 >= 0 and i - 1 >= 0) { - auto cost = field->get_cell((i - 1) / resolution, (j - 1) / resolution); + auto cost = field->get_cell(coord::tile{(i - 1) / resolution, (j - 1) / resolution}); surround.push_back(cost); } if (j < static_cast(field->get_size()) and i - 1 >= 0) { - auto cost = field->get_cell((i - 1) / resolution, j / resolution); + auto cost = field->get_cell(coord::tile{(i - 1) / resolution, j / resolution}); surround.push_back(cost); } if (j < static_cast(field->get_size()) and i < static_cast(field->get_size())) { - auto cost = field->get_cell(i / resolution, j / resolution); + auto cost = field->get_cell(coord::tile{i / resolution, j / resolution}); surround.push_back(cost); } if (j - 1 >= 0 and i < static_cast(field->get_size())) { - auto cost = field->get_cell(i / resolution, (j - 1) / resolution); + auto cost = field->get_cell(coord::tile{i / resolution, (j - 1) / resolution}); surround.push_back(cost); } // use the cost of the most expensive surrounding tile diff --git a/libopenage/pathfinding/flow_field.cpp b/libopenage/pathfinding/flow_field.cpp index 52f4d7738e..96c1a0cd56 100644 --- a/libopenage/pathfinding/flow_field.cpp +++ b/libopenage/pathfinding/flow_field.cpp @@ -5,6 +5,8 @@ #include "error/error.h" #include "log/log.h" +#include "coord/tile.h" +#include "pathfinding/definitions.h" #include "pathfinding/integration_field.h" @@ -26,12 +28,12 @@ size_t FlowField::get_size() const { return this->size; } -flow_t FlowField::get_cell(size_t x, size_t y) const { - return this->cells.at(x + y * this->size); +flow_t FlowField::get_cell(const coord::tile &pos) const { + return this->cells.at(pos.ne + pos.se * this->size); } -flow_dir_t FlowField::get_dir(size_t x, size_t y) const { - return static_cast(this->get_cell(x, y) & FLOW_DIR_MASK); +flow_dir_t FlowField::get_dir(const coord::tile &pos) const { + return static_cast(this->get_cell(pos) & FLOW_DIR_MASK); } void FlowField::build(const std::shared_ptr &integrate_field) { diff --git a/libopenage/pathfinding/flow_field.h b/libopenage/pathfinding/flow_field.h index 5d8ab25580..8389b2a0f7 100644 --- a/libopenage/pathfinding/flow_field.h +++ b/libopenage/pathfinding/flow_field.h @@ -10,7 +10,12 @@ #include "pathfinding/types.h" -namespace openage::path { +namespace openage { +namespace coord { +struct tile; +} // namespace coord + +namespace path { class IntegrationField; class FlowField { @@ -39,22 +44,20 @@ class FlowField { /** * Get the flow field value at a specified position. * - * @param x X coordinate. - * @param y Y coordinate. + * @param pos Coordinates of the cell. * * @return Flowfield value at the specified position. */ - flow_t get_cell(size_t x, size_t y) const; + flow_t get_cell(const coord::tile &pos) const; /** * Get the flow field direction at a specified position. * - * @param x X coordinate. - * @param y Y coordinate. + * @param pos Coordinates of the cell. * * @return Flowfield direction at the specified position. */ - flow_dir_t get_dir(size_t x, size_t y) const; + flow_dir_t get_dir(const coord::tile &pos) const; /** * Build the flow field. @@ -87,4 +90,5 @@ class FlowField { std::vector cells; }; -} // namespace openage::path +} // namespace path +} // namespace openage diff --git a/libopenage/pathfinding/integration_field.cpp b/libopenage/pathfinding/integration_field.cpp index 09cebb0f4d..9881ec0f3c 100644 --- a/libopenage/pathfinding/integration_field.cpp +++ b/libopenage/pathfinding/integration_field.cpp @@ -7,6 +7,7 @@ #include "error/error.h" #include "log/log.h" +#include "coord/tile.h" #include "pathfinding/cost_field.h" #include "pathfinding/definitions.h" @@ -23,8 +24,8 @@ size_t IntegrationField::get_size() const { return this->size; } -const integrate_t &IntegrationField::get_cell(size_t x, size_t y) const { - return this->cells.at(x + y * this->size); +const integrate_t &IntegrationField::get_cell(const coord::tile &pos) const { + return this->cells.at(pos.ne + pos.se * this->size); } const integrate_t &IntegrationField::get_cell(size_t idx) const { @@ -32,8 +33,7 @@ const integrate_t &IntegrationField::get_cell(size_t idx) const { } std::vector IntegrationField::integrate_los(const std::shared_ptr &cost_field, - size_t target_x, - size_t target_y) { + const coord::tile &target) { ENSURE(cost_field->get_size() == this->get_size(), "cost field size " << cost_field->get_size() << "x" << cost_field->get_size() @@ -44,7 +44,7 @@ std::vector IntegrationField::integrate_los(const std::shared_ptr wavefront_blocked; // Target cell index - auto target_idx = target_x + target_y * this->size; + auto target_idx = target.ne + target.se * this->size; // Lookup table for cells that have been found std::unordered_set found; @@ -97,11 +97,11 @@ std::vector IntegrationField::integrate_los(const std::shared_ptrget_los_corners(cost_field, target_x, target_y, x, y); + auto corners = this->get_los_corners(cost_field, target, coord::tile{x, y}); for (auto &corner : corners) { // draw a line from the corner to the edge of the field // to get the cells blocked by the corner - auto blocked_cells = this->bresenhams_line(target_x, target_y, corner.first, corner.second); + auto blocked_cells = this->bresenhams_line(target, corner.first, corner.second); for (auto &blocked_idx : blocked_cells) { if (cost_field->get_cost(blocked_idx) > COST_MIN) { // stop if blocked_idx is impassable @@ -148,8 +148,7 @@ std::vector IntegrationField::integrate_los(const std::shared_ptr &cost_field, - size_t target_x, - size_t target_y) { + const coord::tile &target) { ENSURE(cost_field->get_size() == this->get_size(), "cost field size " << cost_field->get_size() << "x" << cost_field->get_size() @@ -157,7 +156,7 @@ void IntegrationField::integrate_cost(const std::shared_ptr &cost_fie << this->get_size() << "x" << this->get_size()); // Target cell index - auto target_idx = target_x + target_y * this->size; + auto target_idx = target.ne + target.se * this->size; // Move outwards from the target cell, updating the integration field this->cells[target_idx].cost = INTEGRATED_COST_START; @@ -258,12 +257,10 @@ void IntegrationField::update_neighbor(size_t idx, } } -std::vector> IntegrationField::get_los_corners(const std::shared_ptr &cost_field, - size_t target_x, - size_t target_y, - size_t blocker_x, - size_t blocker_y) { - std::vector> corners; +std::vector> IntegrationField::get_los_corners(const std::shared_ptr &cost_field, + const coord::tile &target, + const coord::tile &blocker) { + std::vector> corners; // Get the cost of the blocking cell's neighbors @@ -273,30 +270,30 @@ std::vector> IntegrationField::get_los_corners(const s auto bottom_cost = COST_IMPASSABLE; auto right_cost = COST_IMPASSABLE; - std::pair top_left{blocker_x, blocker_y}; - std::pair top_right{blocker_x + 1, blocker_y}; - std::pair bottom_left{blocker_x, blocker_y + 1}; - std::pair bottom_right{blocker_x + 1, blocker_y + 1}; + std::pair top_left{blocker.ne, blocker.se}; + std::pair top_right{blocker.ne + 1, blocker.se}; + std::pair bottom_left{blocker.ne, blocker.se + 1}; + std::pair bottom_right{blocker.ne + 1, blocker.se + 1}; // Get neighbor costs (if they exist) - if (blocker_y > 0) { - top_cost = cost_field->get_cost(blocker_x, blocker_y - 1); + if (blocker.se > 0) { + top_cost = cost_field->get_cost(coord::tile{blocker.ne, blocker.se - 1}); } - if (blocker_x > 0) { - left_cost = cost_field->get_cost(blocker_x - 1, blocker_y); + if (blocker.ne > 0) { + left_cost = cost_field->get_cost(coord::tile{blocker.ne - 1, blocker.se}); } - if (blocker_y < this->size - 1) { - bottom_cost = cost_field->get_cost(blocker_x, blocker_y + 1); + if (blocker.se < this->size - 1) { + bottom_cost = cost_field->get_cost(coord::tile{blocker.ne, blocker.se + 1}); } - if (blocker_x < this->size - 1) { - right_cost = cost_field->get_cost(blocker_x + 1, blocker_y); + if (blocker.ne < this->size - 1) { + right_cost = cost_field->get_cost(coord::tile{blocker.ne + 1, blocker.se}); } // Check which corners are blocking LOS // TODO: Currently super complicated and could likely be optimized - if (blocker_x == target_x) { + if (blocker.ne == target.ne) { // blocking cell is parallel to target on y-axis - if (blocker_y < target_y) { + if (blocker.se < target.se) { if (left_cost == COST_MIN) { // top corners.push_back(bottom_left); @@ -317,9 +314,9 @@ std::vector> IntegrationField::get_los_corners(const s } } } - else if (blocker_y == target_y) { + else if (blocker.se == target.se) { // blocking cell is parallel to target on x-axis - if (blocker_x < target_x) { + if (blocker.ne < target.ne) { if (top_cost == COST_MIN) { // right corners.push_back(top_right); @@ -342,8 +339,8 @@ std::vector> IntegrationField::get_los_corners(const s } else { // blocking cell is diagonal to target on - if (blocker_x < target_x) { - if (blocker_y < target_y) { + if (blocker.ne < target.ne) { + if (blocker.se < target.se) { // top and right if (top_cost == COST_MIN and right_cost == COST_MIN) { // right @@ -367,7 +364,7 @@ std::vector> IntegrationField::get_los_corners(const s } } else { - if (blocker_y < target_y) { + if (blocker.se < target.se) { // top and left if (top_cost == COST_MIN and left_cost == COST_MIN) { // left @@ -395,8 +392,7 @@ std::vector> IntegrationField::get_los_corners(const s return corners; } -std::vector IntegrationField::bresenhams_line(int target_x, - int target_y, +std::vector IntegrationField::bresenhams_line(const coord::tile &target, int corner_x, int corner_y) { std::vector cells; @@ -411,8 +407,8 @@ std::vector IntegrationField::bresenhams_line(int target_x, // target coordinates // offset by 0.5 to get the center of the cell - double tx = target_x + 0.5; - double ty = target_y + 0.5; + double tx = target.ne + 0.5; + double ty = target.se + 0.5; // slope of the line double dx = std::abs(tx - corner_x); diff --git a/libopenage/pathfinding/integration_field.h b/libopenage/pathfinding/integration_field.h index dc5821c856..3e01275d9d 100644 --- a/libopenage/pathfinding/integration_field.h +++ b/libopenage/pathfinding/integration_field.h @@ -11,7 +11,12 @@ #include "pathfinding/types.h" -namespace openage::path { +namespace openage { +namespace coord { +struct tile; +} // namespace coord + +namespace path { class CostField; /** @@ -36,11 +41,10 @@ class IntegrationField { /** * Get the integration value at a specified position. * - * @param x X coordinate. - * @param y Y coordinate. + * @param pos Coordinates of the cell. * @return Integration value at the specified position. */ - const integrate_t &get_cell(size_t x, size_t y) const; + const integrate_t &get_cell(const coord::tile &pos) const; /** * Get the integration value at a specified position. @@ -57,26 +61,22 @@ class IntegrationField { * can be used as a starting point for the cost integration. * * @param cost_field Cost field to integrate. - * @param target_x X coordinate of the target cell. - * @param target_y Y coordinate of the target cell. + * @param target Coordinates of the target cell. * * @return Cells flagged as "wavefront blocked". */ std::vector integrate_los(const std::shared_ptr &cost_field, - size_t target_x, - size_t target_y); + const coord::tile &target); /** * Calculate the cost integration field starting from a target cell. * * @param cost_field Cost field to integrate. - * @param target_x X coordinate of the target cell. - * @param target_y Y coordinate of the target cell. + * @param target Coordinates of the target cell. * @param start_cells Cells flagged as "wavefront blocked" from a LOS pass. */ void integrate_cost(const std::shared_ptr &cost_field, - size_t target_x, - size_t target_y); + const coord::tile &target); /** * Calculate the cost integration field starting from a wavefront. @@ -121,18 +121,14 @@ class IntegrationField { * Get the LOS corners around a cell. * * @param cost_field Cost field to integrate. - * @param target_x X cell coordinate of the target. - * @param target_y Y cell coordinate of the target. - * @param blocker_x X cell coordinate of the cell blocking LOS. - * @param blocker_y Y cell coordinate of the cell blocking LOS. + * @param target Cell coordinates of the target. + * @param blocker Cell coordinates of the cell blocking LOS. * * @return Field coordinates of the LOS corners. */ - std::vector> get_los_corners(const std::shared_ptr &cost_field, - size_t target_x, - size_t target_y, - size_t blocker_x, - size_t blocker_y); + std::vector> get_los_corners(const std::shared_ptr &cost_field, + const coord::tile &target, + const coord::tile &blocker); /** * Get the cells in a bresenham's line between the corner cell and the field edge. @@ -142,13 +138,13 @@ class IntegrationField { * the cells between two arbitrary points. We do this because the intersection * point with the field edge is unknown. * - * @param target_x X cell coordinate of the target. - * @param target_y Y cell coordinate of the target. + * @param target Cell coordinates of the target. * @param corner_x X field coordinate edge of the LOS corner. * @param corner_y Y field coordinate edge of the LOS corner. + * + * @return Cell indices of the LOS line. */ - std::vector bresenhams_line(int target_x, - int target_y, + std::vector bresenhams_line(const coord::tile &target, int corner_x, int corner_y); @@ -163,4 +159,5 @@ class IntegrationField { std::vector cells; }; -} // namespace openage::path +} // namespace path +} // namespace openage diff --git a/libopenage/pathfinding/integrator.cpp b/libopenage/pathfinding/integrator.cpp index 2c083b5615..3e9ecbe902 100644 --- a/libopenage/pathfinding/integrator.cpp +++ b/libopenage/pathfinding/integrator.cpp @@ -13,11 +13,11 @@ void Integrator::set_cost_field(const std::shared_ptr &cost_field) { this->cost_field = cost_field; } -std::shared_ptr Integrator::build(size_t target_x, size_t target_y) { +std::shared_ptr Integrator::build(const coord::tile &target) { auto flow_field = std::make_shared(this->cost_field->get_size()); auto integrate_field = std::make_shared(this->cost_field->get_size()); - auto wavefront_blocked = integrate_field->integrate_los(this->cost_field, target_x, target_y); + auto wavefront_blocked = integrate_field->integrate_los(this->cost_field, target); integrate_field->integrate_cost(this->cost_field, std::move(wavefront_blocked)); flow_field->build(integrate_field); diff --git a/libopenage/pathfinding/integrator.h b/libopenage/pathfinding/integrator.h index a00eaa2700..fc0d433c1d 100644 --- a/libopenage/pathfinding/integrator.h +++ b/libopenage/pathfinding/integrator.h @@ -6,7 +6,12 @@ #include -namespace openage::path { +namespace openage { +namespace coord { +struct tile; +} // namespace coord + +namespace path { class CostField; class FlowField; @@ -28,12 +33,11 @@ class Integrator { /** * Build the flow field for a target cell. * - * @param target_x X coordinate of the target cell. - * @param target_y Y coordinate of the target cell. + * @param target Coordinates of the target cell. * * @return Flow field. */ - std::shared_ptr build(size_t target_x, size_t target_y); + std::shared_ptr build(const coord::tile &target); private: /** @@ -42,4 +46,5 @@ class Integrator { std::shared_ptr cost_field; }; -} // namespace openage::path +} // namespace path +} // namespace openage diff --git a/libopenage/pathfinding/portal.cpp b/libopenage/pathfinding/portal.cpp index 0b3b472667..72d4a0ee55 100644 --- a/libopenage/pathfinding/portal.cpp +++ b/libopenage/pathfinding/portal.cpp @@ -11,20 +11,16 @@ Portal::Portal(portal_id_t id, sector_id_t sector0, sector_id_t sector1, PortalDirection direction, - size_t cell_start_x, - size_t cell_start_y, - size_t cell_end_x, - size_t cell_end_y) : + const coord::tile &cell_start, + const coord::tile &cell_end) : id{id}, sector0{sector0}, sector1{sector1}, sector0_exits{}, sector1_exits{}, direction{direction}, - cell_start_x{cell_start_x}, - cell_start_y{cell_start_y}, - cell_end_x{cell_end_x}, - cell_end_y{cell_end_y} { + cell_start{cell_start}, + cell_end{cell_end} { } portal_id_t Portal::get_id() const { @@ -60,7 +56,7 @@ sector_id_t Portal::get_exit_sector(sector_id_t entry_sector) const { return this->sector0; } -std::pair Portal::get_entry_start(sector_id_t entry_sector) const { +const coord::tile 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) { @@ -70,7 +66,7 @@ std::pair Portal::get_entry_start(sector_id_t entry_sector) cons return this->get_sector1_start(); } -std::pair Portal::get_entry_end(sector_id_t entry_sector) const { +const coord::tile 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) { @@ -80,7 +76,7 @@ std::pair Portal::get_entry_end(sector_id_t entry_sector) const return this->get_sector1_end(); } -std::pair Portal::get_exit_start(sector_id_t entry_sector) const { +const coord::tile 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) { @@ -90,7 +86,7 @@ std::pair Portal::get_exit_start(sector_id_t entry_sector) const return this->get_sector0_start(); } -std::pair Portal::get_exit_end(sector_id_t entry_sector) const { +const coord::tile 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) { @@ -104,20 +100,26 @@ PortalDirection Portal::get_direction() const { return this->direction; } -std::pair Portal::get_sector0_start() const { - return {this->cell_start_x, this->cell_start_y}; +const coord::tile &Portal::get_sector0_start() const { + return this->cell_start; } -std::pair Portal::get_sector0_end() const { - return {this->cell_end_x, this->cell_end_y}; +const coord::tile &Portal::get_sector0_end() const { + return this->cell_end; } -std::pair Portal::get_sector1_start() const { - return {this->cell_end_x, this->cell_end_y}; +const coord::tile Portal::get_sector1_start() const { + if (this->direction == PortalDirection::NORTH_SOUTH) { + return {this->cell_start.ne, 0}; + } + return {0, this->cell_start.se}; } -std::pair Portal::get_sector1_end() const { - return {this->cell_start_x, this->cell_start_y}; +const coord::tile Portal::get_sector1_end() const { + if (this->direction == PortalDirection::NORTH_SOUTH) { + return {this->cell_end.ne, 0}; + } + return {0, this->cell_end.se}; } } // namespace openage::path diff --git a/libopenage/pathfinding/portal.h b/libopenage/pathfinding/portal.h index 170fd463ca..4855d0eb89 100644 --- a/libopenage/pathfinding/portal.h +++ b/libopenage/pathfinding/portal.h @@ -6,6 +6,7 @@ #include #include +#include "coord/tile.h" #include "pathfinding/types.h" @@ -48,19 +49,15 @@ class Portal { * @param sector1 Second sector connected by the portal. * Must be south or west on the grid in relation to sector 0. * @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. + * @param cell_start Start cell coordinate in sector 0. + * @param cell_end End cell coordinate in sector 0. */ Portal(portal_id_t id, sector_id_t sector0, sector_id_t sector1, PortalDirection direction, - size_t cell_start_x, - size_t cell_start_y, - size_t cell_end_x, - size_t cell_end_y); + const coord::tile &cell_start, + const coord::tile &cell_end); ~Portal() = default; @@ -106,7 +103,7 @@ class Portal { * * @return Cost field of the sector from which the portal is entered. */ - std::pair get_entry_start(sector_id_t entry_sector) const; + const coord::tile get_entry_start(sector_id_t entry_sector) const; /** * Get the cell coordinates of the start of the portal in the entry sector. @@ -115,7 +112,7 @@ class Portal { * * @return Cell coordinates of the start of the portal in the entry sector. */ - std::pair get_entry_end(sector_id_t entry_sector) const; + const coord::tile get_entry_end(sector_id_t entry_sector) const; /** * Get the cell coordinates of the start of the portal in the exit sector. @@ -124,7 +121,7 @@ class Portal { * * @return Cell coordinates of the start of the portal in the exit sector. */ - std::pair get_exit_start(sector_id_t entry_sector) const; + const coord::tile get_exit_start(sector_id_t entry_sector) const; /** * Get the cell coordinates of the end of the portal in the exit sector. @@ -133,7 +130,7 @@ class Portal { * * @return Cell coordinates of the end of the portal in the exit sector. */ - std::pair get_exit_end(sector_id_t entry_sector) const; + const coord::tile get_exit_end(sector_id_t entry_sector) const; /** * Get the direction of the portal from sector 0 to sector 1. @@ -148,28 +145,28 @@ class Portal { * * @return Start cell coordinates of the portal. */ - std::pair get_sector0_start() const; + const coord::tile &get_sector0_start() const; /** * Get the end cell coordinates of the portal. * * @return End cell coordinates of the portal. */ - std::pair get_sector0_end() const; + const coord::tile &get_sector0_end() const; /** * Get the start cell coordinates of the portal. * * @return Start cell coordinates of the portal. */ - std::pair get_sector1_start() const; + const coord::tile get_sector1_start() const; /** * Get the end cell coordinates of the portal. * * @return End cell coordinates of the portal. */ - std::pair get_sector1_end() const; + const coord::tile get_sector1_end() const; /** * ID of the portal. @@ -202,23 +199,13 @@ class Portal { PortalDirection direction; /** - * Start cell x coordinate. + * Start cell coordinate. */ - size_t cell_start_x; + coord::tile cell_start; /** - * Start cell y coordinate. + * End cell coordinate. */ - size_t cell_start_y; - - /** - * End cell x coordinate. - */ - size_t cell_end_x; - - /** - * End cell y coordinate. - */ - size_t cell_end_y; + coord::tile cell_end; }; } // namespace openage::path diff --git a/libopenage/pathfinding/sector.cpp b/libopenage/pathfinding/sector.cpp index 554a5518b1..05e8c1460f 100644 --- a/libopenage/pathfinding/sector.cpp +++ b/libopenage/pathfinding/sector.cpp @@ -4,6 +4,7 @@ #include "error/error.h" +#include "coord/tile.h" #include "pathfinding/cost_field.h" #include "pathfinding/definitions.h" @@ -52,8 +53,8 @@ std::vector> Sector::find_portals(const std::shared_ptr< 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 (this->cost_field->get_cost(coord::tile{x, this->cost_field->get_size() - 1}) != COST_IMPASSABLE + and other_cost->get_cost(coord::tile{x, 0}) != COST_IMPASSABLE) { if (not passable_edge) { start = x; passable_edge = true; @@ -67,10 +68,8 @@ std::vector> Sector::find_portals(const std::shared_ptr< this->id, other->get_id(), direction, - start, - this->cost_field->get_size() - 1, - x - 1, - 0)); + coord::tile{start, this->cost_field->get_size() - 1}, + coord::tile{x - 1, 0})); passable_edge = false; next_id += 1; } @@ -82,8 +81,8 @@ std::vector> Sector::find_portals(const std::shared_ptr< 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 (this->cost_field->get_cost(coord::tile{this->cost_field->get_size() - 1, y}) != COST_IMPASSABLE + and other_cost->get_cost(coord::tile{0, y}) != COST_IMPASSABLE) { if (not passable_edge) { start = y; passable_edge = true; @@ -97,10 +96,8 @@ std::vector> Sector::find_portals(const std::shared_ptr< this->id, other->get_id(), direction, - this->cost_field->get_size() - 1, - start, - 0, - y - 1)); + coord::tile{this->cost_field->get_size() - 1, start}, + coord::tile{0, y - 1})); passable_edge = false; next_id += 1; } diff --git a/libopenage/pathfinding/tests.cpp b/libopenage/pathfinding/tests.cpp index e31e87498e..ab62d2ff2f 100644 --- a/libopenage/pathfinding/tests.cpp +++ b/libopenage/pathfinding/tests.cpp @@ -3,6 +3,7 @@ #include "log/log.h" #include "testing/testing.h" +#include "coord/tile.h" #include "pathfinding/cost_field.h" #include "pathfinding/definitions.h" #include "pathfinding/flow_field.h" @@ -10,6 +11,7 @@ #include "pathfinding/integrator.h" #include "pathfinding/types.h" + namespace openage { namespace path { namespace tests { @@ -26,7 +28,7 @@ void flow_field() { // Test the different field types { auto integration_field = std::make_shared(3); - integration_field->integrate_cost(cost_field, 2, 2); + integration_field->integrate_cost(cost_field, coord::tile{2, 2}); auto int_cells = integration_field->get_cells(); // The integration field should look like: @@ -84,7 +86,7 @@ void flow_field() { integrator->set_cost_field(cost_field); // Build the flow field - auto flow_field = integrator->build(2, 2); + auto flow_field = integrator->build(coord::tile{2, 2}); auto ff_cells = flow_field->get_cells(); // The flow field for targeting (2, 2) hould look like this: From ca05032d4af69bd6a65374c9ce8a2f424d4f0de3 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sat, 23 Mar 2024 17:06:52 +0100 Subject: [PATCH 058/112] path: Store grid size in vector. --- libopenage/pathfinding/grid.cpp | 29 ++++++++++++++--------------- libopenage/pathfinding/grid.h | 24 ++++++++---------------- 2 files changed, 22 insertions(+), 31 deletions(-) diff --git a/libopenage/pathfinding/grid.cpp b/libopenage/pathfinding/grid.cpp index 6ad53178f0..8fa452630f 100644 --- a/libopenage/pathfinding/grid.cpp +++ b/libopenage/pathfinding/grid.cpp @@ -9,27 +9,26 @@ namespace openage::path { -Grid::Grid(grid_id_t id, size_t width, size_t height, size_t sector_size) : +Grid::Grid(grid_id_t id, + const util::Vector2s &size, + size_t sector_size) : id{id}, - 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)); + size{size} { + for (size_t y = 0; y < size[1]; y++) { + for (size_t x = 0; x < size[0]; x++) { + this->sectors.push_back(std::make_shared(x + y * this->size[0], sector_size)); } } } Grid::Grid(grid_id_t id, - size_t width, - size_t height, + const util::Vector2s &size, std::vector> &§ors) : id{id}, - width{width}, - height{height}, + size{size}, sectors{std::move(sectors)} { - ENSURE(this->sectors.size() == width * height, - "Grid has size " << width << "x" << height << " (" << width * height << " sectors), " + ENSURE(this->sectors.size() == size[0] * size[1], + "Grid has size " << size[0] << "x" << size[1] << " (" << size[0] * size[1] << " sectors), " << "but only " << this->sectors.size() << " sectors were provided"); } @@ -37,12 +36,12 @@ grid_id_t Grid::get_id() const { return this->id; } -std::pair Grid::get_size() const { - return {this->width, this->height}; +const util::Vector2s &Grid::get_size() const { + return this->size; } const std::shared_ptr &Grid::get_sector(size_t x, size_t y) { - return this->sectors.at(x + y * this->width); + return this->sectors.at(x + y * this->size[0]); } const std::shared_ptr &Grid::get_sector(sector_id_t id) const { diff --git a/libopenage/pathfinding/grid.h b/libopenage/pathfinding/grid.h index adf030cf53..2daa0eab12 100644 --- a/libopenage/pathfinding/grid.h +++ b/libopenage/pathfinding/grid.h @@ -8,6 +8,7 @@ #include #include "pathfinding/types.h" +#include "util/vector.h" namespace openage::path { @@ -22,26 +23,22 @@ class Grid { * Create a new empty grid of width x height sectors with a specified size. * * @param id ID of the grid. - * @param width Width of the grid. - * @param height Height of the grid. + * @param size Size of the grid (width x height). * @param sector_size Side length of each sector. */ Grid(grid_id_t id, - size_t width, - size_t height, + const util::Vector2s &size, size_t sector_size); /** * Create a grid of width x height sectors from a list of existing sectors. * * @param id ID of the grid. - * @param width Width of the grid. - * @param height Height of the grid. + * @param size Size of the grid (width x height). * @param sectors Existing sectors. */ Grid(grid_id_t id, - size_t width, - size_t height, + const util::Vector2s &size, std::vector> &§ors); /** @@ -56,7 +53,7 @@ class Grid { * * @return Size of the grid (width x height). */ - std::pair get_size() const; + const util::Vector2s &get_size() const; /** * Get the sector at a specified position. @@ -84,14 +81,9 @@ class Grid { grid_id_t id; /** - * Width of the grid. + * Size of the grid (width x height). */ - size_t width; - - /** - * Height of the grid. - */ - size_t height; + util::Vector2s size; /** * Sectors of the grid. From f943d86ec9983681b57dd537df62ec39acaecbb5 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sat, 23 Mar 2024 23:20:49 +0100 Subject: [PATCH 059/112] path: Demo for pathfinding grid. --- libopenage/pathfinding/demo/CMakeLists.txt | 1 + libopenage/pathfinding/demo/demo_0.h | 2 +- libopenage/pathfinding/demo/demo_1.cpp | 62 +++++++ libopenage/pathfinding/demo/demo_1.h | 197 +++++++++++++++++++++ libopenage/pathfinding/demo/tests.cpp | 5 + libopenage/pathfinding/grid.cpp | 4 + libopenage/pathfinding/grid.h | 7 + 7 files changed, 277 insertions(+), 1 deletion(-) create mode 100644 libopenage/pathfinding/demo/demo_1.cpp create mode 100644 libopenage/pathfinding/demo/demo_1.h diff --git a/libopenage/pathfinding/demo/CMakeLists.txt b/libopenage/pathfinding/demo/CMakeLists.txt index 27ad003577..938d6ee3f0 100644 --- a/libopenage/pathfinding/demo/CMakeLists.txt +++ b/libopenage/pathfinding/demo/CMakeLists.txt @@ -1,5 +1,6 @@ add_sources(libopenage demo_0.cpp + demo_1.cpp tests.cpp ) diff --git a/libopenage/pathfinding/demo/demo_0.h b/libopenage/pathfinding/demo/demo_0.h index 0dcd115c0e..f84472ee54 100644 --- a/libopenage/pathfinding/demo/demo_0.h +++ b/libopenage/pathfinding/demo/demo_0.h @@ -35,7 +35,7 @@ class FlowField; namespace tests { /** - * Show the pathfinding functionality of the path module: + * Show the functionality of the different flowfield types: * - Cost field * - Integration field * - Flow field diff --git a/libopenage/pathfinding/demo/demo_1.cpp b/libopenage/pathfinding/demo/demo_1.cpp new file mode 100644 index 0000000000..171eeb6f47 --- /dev/null +++ b/libopenage/pathfinding/demo/demo_1.cpp @@ -0,0 +1,62 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#include "demo_1.h" + +#include "pathfinding/cost_field.h" +#include "pathfinding/grid.h" +#include "pathfinding/portal.h" +#include "pathfinding/sector.h" + + +namespace openage::path::tests { + +void path_demo_1(const util::Path &path) { + auto grid = std::make_shared(0, util::Vector2s{4, 3}, 10); + + // Initialize the cost field for each sector. + for (auto sector : grid->get_sectors()) { + auto cost_field = sector->get_cost_field(); + auto sector_cost = sectors_cost.at(sector->get_id()); + cost_field->set_costs(std::move(sector_cost)); + } + + // Initialize portals between sectors. + auto grid_size = grid->get_size(); + auto portal_id = 0; + for (size_t y = 0; y < grid_size[1]; y++) { + for (size_t x = 0; x < grid_size[0]; x++) { + auto sector = grid->get_sector(x, y); + + if (x < grid_size[0] - 1) { + auto neighbor = grid->get_sector(x + 1, y); + auto portals = sector->find_portals(neighbor, PortalDirection::EAST_WEST, portal_id); + for (auto portal : portals) { + sector->add_portal(portal); + neighbor->add_portal(portal); + } + portal_id += portals.size(); + } + if (y < grid_size[1] - 1) { + auto neighbor = grid->get_sector(x, y + 1); + auto portals = sector->find_portals(neighbor, PortalDirection::NORTH_SOUTH, portal_id); + for (auto portal : portals) { + sector->add_portal(portal); + neighbor->add_portal(portal); + } + portal_id += portals.size(); + } + } + } + + for (auto sector : grid->get_sectors()) { + log::log(MSG(info) << "Sector " << sector->get_id() << " has " << sector->get_portals().size() << " portals."); + for (auto portal : sector->get_portals()) { + log::log(MSG(info) << " Portal " << portal->get_id() << " connects sectors " + << sector->get_id() << " and " + << portal->get_exit_sector(sector->get_id()) << " at tiles " + << portal->get_entry_start(sector->get_id()) << " and " + << portal->get_entry_end(sector->get_id())); + } + } +} +} // namespace openage::path::tests diff --git a/libopenage/pathfinding/demo/demo_1.h b/libopenage/pathfinding/demo/demo_1.h new file mode 100644 index 0000000000..e74f79e487 --- /dev/null +++ b/libopenage/pathfinding/demo/demo_1.h @@ -0,0 +1,197 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#pragma once + +#include "pathfinding/definitions.h" +#include "util/path.h" + + +namespace openage::path::tests { + + +/** + * Show the functionality of the high-level pathfinder: + * - Grids + * - Sectors + * - Portals + * + * Visualizes the pathfinding results using our rendering backend. + * + * @param path Path to the project rootdir. + */ +void path_demo_1(const util::Path &path); + + +// Cost for the sectors in the grid +// taken from Figure 23.1 in "Crowd Pathfinding and Steering Using Flow Field Tiles" +const std::vector> sectors_cost = { + { + // clang-format off + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 255, 255, 255, 255, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 255, 255, 255, 255, 1, 1, + 1, 1, 1, 255, 255, 255, 255, 255, 1, 1, + 1, 1, 255, 255, 255, 255, 255, 1, 1, 1, + 1, 1, 255, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 255, 1, 1, 1, 1, 1, 1, 1, + // clang-format on + }, + { + // clang-format off + 1, 1, 255, 255, 255, 255, 255, 255, 255, 255, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 255, 1, 1, 255, 1, 1, 1, + 1, 1, 1, 255, 1, 255, 255, 1, 1, 1, + 1, 1, 1, 255, 255, 255, 255, 255, 255, 255, + 1, 1, 1, 1, 255, 255, 255, 1, 1, 1, + 1, 1, 1, 1, 255, 255, 1, 1, 1, 1, + 1, 1, 1, 255, 1, 1, 1, 1, 1, 1, + // clang-format on + }, + { + // clang-format off + 255, 255, 1, 1, 1, 1, 1, 255, 255, 255, + 1, 1, 1, 1, 1, 1, 1, 1, 255, 255, + 1, 1, 1, 1, 1, 1, 1, 1, 255, 255, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 255, 255, 255, 1, 1, 1, + 1, 1, 1, 1, 1, 255, 255, 255, 255, 255, + 1, 1, 1, 1, 255, 255, 255, 255, 255, 1, + 1, 1, 1, 1, 255, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 255, 1, 1, 1, 1, 255, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + // clang-format on + }, + { + // clang-format off + 255, 255, 1, 1, 1, 1, 1, 1, 1, 1, + 255, 255, 1, 1, 1, 1, 1, 1, 1, 1, + 255, 255, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 255, 1, 1, 1, 1, 1, 1, + 1, 1, 255, 255, 1, 1, 1, 1, 1, 1, + 255, 255, 255, 255, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + // clang-format on + }, + { + // clang-format off + 1, 1, 255, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 255, 1, 1, 1, 1, 1, 255, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 255, 1, 1, 1, 1, 1, + 1, 1, 1, 255, 255, 1, 1, 1, 1, 1, + 1, 1, 255, 1, 255, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 255, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 255, 255, 255, 255, 255, 255, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 255, 255, 255, + // clang-format on + }, + { + // clang-format off + 1, 1, 1, 255, 1, 1, 1, 1, 1, 255, + 1, 1, 1, 255, 1, 255, 255, 255, 255, 255, + 1, 1, 1, 255, 1, 1, 1, 1, 255, 255, + 1, 1, 1, 255, 1, 1, 1, 1, 255, 255, + 1, 255, 255, 1, 1, 1, 1, 1, 255, 255, + 1, 1, 255, 1, 1, 255, 1, 1, 1, 1, + 1, 1, 255, 1, 1, 255, 1, 1, 1, 1, + 255, 255, 255, 255, 255, 255, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 255, 1, 1, 1, 1, 1, 1, 1, 1, 1, + // clang-format on + }, + { + // clang-format off + 255, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 255, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 255, 1, 1, 1, 1, 1, 1, 1, 255, 255, + 255, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 255, 1, 1, 1, 1, 1, 255, 1, 1, 1, + 255, 1, 1, 1, 1, 1, 255, 1, 1, 1, + 255, 1, 1, 1, 1, 1, 255, 1, 1, 1, + 255, 1, 1, 1, 1, 1, 255, 255, 255, 255, + 255, 1, 1, 1, 255, 255, 255, 255, 255, 1, + 255, 1, 1, 1, 1, 1, 1, 1, 1, 1, + // clang-format on + }, + { + // clang-format off + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 255, 255, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 255, 255, 255, 255, 255, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 255, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 255, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + // clang-format on + }, + { + // clang-format off + 1, 1, 255, 1, 1, 1, 1, 255, 255, 255, + 1, 1, 255, 1, 1, 1, 255, 255, 1, 1, + 1, 1, 1, 1, 1, 255, 255, 255, 1, 1, + 1, 1, 1, 1, 255, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 255, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 255, + 1, 1, 1, 1, 1, 1, 1, 1, 255, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + // clang-format on + }, + { + // clang-format off + 255, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 255, 1, 1, 1, 1, 1, 1, + 1, 1, 255, 255, 255, 255, 255, 255, 1, 1, + 1, 255, 255, 255, 1, 1, 1, 1, 1, 1, + 255, 255, 255, 255, 1, 1, 1, 1, 1, 1, + 255, 1, 1, 1, 1, 1, 1, 1, 1, 255, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + // clang-format on + }, + { + // clang-format off + 255, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 255, 255, 255, 255, 255, 1, 1, + 1, 1, 255, 255, 1, 1, 1, 255, 1, 1, + 1, 255, 255, 1, 1, 1, 1, 1, 1, 1, + 255, 255, 1, 1, 1, 1, 1, 1, 1, 1, + 255, 255, 1, 1, 1, 255, 255, 255, 1, 1, + 1, 1, 1, 1, 1, 1, 255, 1, 1, 1, + 1, 1, 1, 255, 255, 255, 255, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + // clang-format on + }, + { + // clang-format off + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + // clang-format on + }}; + +} // namespace openage::path::tests diff --git a/libopenage/pathfinding/demo/tests.cpp b/libopenage/pathfinding/demo/tests.cpp index da6e59479f..633dddb621 100644 --- a/libopenage/pathfinding/demo/tests.cpp +++ b/libopenage/pathfinding/demo/tests.cpp @@ -5,6 +5,7 @@ #include "log/log.h" #include "pathfinding/demo/demo_0.h" +#include "pathfinding/demo/demo_1.h" namespace openage::path::tests { @@ -15,6 +16,10 @@ void path_demo(int demo_id, const util::Path &path) { path_demo_0(path); break; + case 1: + path_demo_1(path); + break; + default: log::log(MSG(err) << "Unknown pathfinding demo requested: " << demo_id << "."); break; diff --git a/libopenage/pathfinding/grid.cpp b/libopenage/pathfinding/grid.cpp index 8fa452630f..d0ee691774 100644 --- a/libopenage/pathfinding/grid.cpp +++ b/libopenage/pathfinding/grid.cpp @@ -48,4 +48,8 @@ const std::shared_ptr &Grid::get_sector(sector_id_t id) const { return this->sectors.at(id); } +const std::vector> &Grid::get_sectors() const { + return this->sectors; +} + } // namespace openage::path diff --git a/libopenage/pathfinding/grid.h b/libopenage/pathfinding/grid.h index 2daa0eab12..4cd2a97b7b 100644 --- a/libopenage/pathfinding/grid.h +++ b/libopenage/pathfinding/grid.h @@ -74,6 +74,13 @@ class Grid { */ const std::shared_ptr &get_sector(sector_id_t id) const; + /** + * Get the sectors of the grid. + * + * @return Sectors of the grid. + */ + const std::vector> &get_sectors() const; + private: /** * ID of the grid. From b3b620ba99293342ecab913006654ee92300f9da Mon Sep 17 00:00:00 2001 From: heinezen Date: Sat, 23 Mar 2024 23:52:39 +0100 Subject: [PATCH 060/112] path: Fix portals not being created if they end in a corner. --- libopenage/pathfinding/demo/demo_1.cpp | 2 +- libopenage/pathfinding/sector.cpp | 117 ++++++++++++++----------- 2 files changed, 69 insertions(+), 50 deletions(-) diff --git a/libopenage/pathfinding/demo/demo_1.cpp b/libopenage/pathfinding/demo/demo_1.cpp index 171eeb6f47..e4dfd3aa6f 100644 --- a/libopenage/pathfinding/demo/demo_1.cpp +++ b/libopenage/pathfinding/demo/demo_1.cpp @@ -51,7 +51,7 @@ void path_demo_1(const util::Path &path) { for (auto sector : grid->get_sectors()) { log::log(MSG(info) << "Sector " << sector->get_id() << " has " << sector->get_portals().size() << " portals."); for (auto portal : sector->get_portals()) { - log::log(MSG(info) << " Portal " << portal->get_id() << " connects sectors " + log::log(MSG(info) << "\tPortal " << portal->get_id() << " connects sectors " << sector->get_id() << " and " << portal->get_exit_sector(sector->get_id()) << " at tiles " << portal->get_entry_start(sector->get_id()) << " and " diff --git a/libopenage/pathfinding/sector.cpp b/libopenage/pathfinding/sector.cpp index 05e8c1460f..0b826ce16c 100644 --- a/libopenage/pathfinding/sector.cpp +++ b/libopenage/pathfinding/sector.cpp @@ -48,63 +48,82 @@ std::vector> Sector::find_portals(const std::shared_ptr< 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(coord::tile{x, this->cost_field->get_size() - 1}) != COST_IMPASSABLE - and other_cost->get_cost(coord::tile{x, 0}) != COST_IMPASSABLE) { - if (not passable_edge) { - start = x; - passable_edge = true; - } - } - else { - if (passable_edge) { - result.push_back( - std::make_shared( - next_id, - this->id, - other->get_id(), - direction, - coord::tile{start, this->cost_field->get_size() - 1}, - coord::tile{x - 1, 0})); - passable_edge = false; - next_id += 1; - } + size_t start; + bool passable_edge; + for (size_t i = 0; i < this->cost_field->get_size(); ++i) { + auto coord_this = coord::tile{0, 0}; + auto coord_other = coord::tile{0, 0}; + if (direction == PortalDirection::NORTH_SOUTH) { + // right edge; top to bottom + coord_this = coord::tile{i, this->cost_field->get_size() - 1}; + coord_other = coord::tile{i, 0}; + } + else if (direction == PortalDirection::EAST_WEST) { + // bottom edge; east to west + coord_this = coord::tile{this->cost_field->get_size() - 1, i}; + coord_other = coord::tile{0, i}; + } + + if (this->cost_field->get_cost(coord_this) != COST_IMPASSABLE + and other_cost->get_cost(coord_other) != COST_IMPASSABLE) { + if (not passable_edge) { + start = i; + passable_edge = true; } } - } - 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(coord::tile{this->cost_field->get_size() - 1, y}) != COST_IMPASSABLE - and other_cost->get_cost(coord::tile{0, y}) != COST_IMPASSABLE) { - if (not passable_edge) { - start = y; - passable_edge = true; + else { + if (passable_edge) { + auto coord_start = coord::tile{0, 0}; + auto coord_end = coord::tile{0, 0}; + if (direction == PortalDirection::NORTH_SOUTH) { + // right edge; top to bottom + coord_start = coord::tile{start, this->cost_field->get_size() - 1}; + coord_end = coord::tile{i - 1, this->cost_field->get_size() - 1}; } - } - else { - if (passable_edge) { - result.push_back( - std::make_shared( - next_id, - this->id, - other->get_id(), - direction, - coord::tile{this->cost_field->get_size() - 1, start}, - coord::tile{0, y - 1})); - passable_edge = false; - next_id += 1; + else if (direction == PortalDirection::EAST_WEST) { + // bottom edge; east to west + coord_start = coord::tile{this->cost_field->get_size() - 1, start}; + coord_end = coord::tile{this->cost_field->get_size() - 1, i - 1}; } + + result.push_back( + std::make_shared( + next_id, + this->id, + other->get_id(), + direction, + coord_start, + coord_end)); + passable_edge = false; + next_id += 1; } } } + // recheck for the last tile on the edge + // because it may be the end of a portal + if (passable_edge) { + auto coord_start = coord::tile{0, 0}; + auto coord_end = coord::tile{0, 0}; + if (direction == PortalDirection::NORTH_SOUTH) { + coord_start = coord::tile{start, this->cost_field->get_size() - 1}; + coord_end = coord::tile{this->cost_field->get_size() - 1, this->cost_field->get_size() - 1}; + } + else if (direction == PortalDirection::EAST_WEST) { + coord_start = coord::tile{this->cost_field->get_size() - 1, start}; + coord_end = coord::tile{this->cost_field->get_size() - 1, this->cost_field->get_size() - 1}; + } + + result.push_back( + std::make_shared( + next_id, + this->id, + other->get_id(), + direction, + coord_start, + coord_end)); + } + return result; } From b382abb8fcc05886338e0fa4dce03f107de7c038 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 24 Mar 2024 03:18:38 +0100 Subject: [PATCH 061/112] path: Connect portals between sectors. --- libopenage/pathfinding/demo/demo_1.cpp | 15 ++- libopenage/pathfinding/portal.cpp | 9 ++ libopenage/pathfinding/portal.h | 9 ++ libopenage/pathfinding/sector.cpp | 128 ++++++++++++++++++++++++- libopenage/pathfinding/sector.h | 2 +- 5 files changed, 155 insertions(+), 8 deletions(-) diff --git a/libopenage/pathfinding/demo/demo_1.cpp b/libopenage/pathfinding/demo/demo_1.cpp index e4dfd3aa6f..3954ba80b8 100644 --- a/libopenage/pathfinding/demo/demo_1.cpp +++ b/libopenage/pathfinding/demo/demo_1.cpp @@ -48,14 +48,19 @@ void path_demo_1(const util::Path &path) { } } + for (auto sector : grid->get_sectors()) { + sector->connect_exits(); + } + for (auto sector : grid->get_sectors()) { log::log(MSG(info) << "Sector " << sector->get_id() << " has " << sector->get_portals().size() << " portals."); for (auto portal : sector->get_portals()) { - log::log(MSG(info) << "\tPortal " << portal->get_id() << " connects sectors " - << sector->get_id() << " and " - << portal->get_exit_sector(sector->get_id()) << " at tiles " - << portal->get_entry_start(sector->get_id()) << " and " - << portal->get_entry_end(sector->get_id())); + log::log(MSG(info) << " Portal " << portal->get_id() << ":"); + log::log(MSG(info) << " Connects sectors: " << sector->get_id() << " to " + << portal->get_exit_sector(sector->get_id())); + log::log(MSG(info) << " Entry start: " << portal->get_entry_start(sector->get_id())); + log::log(MSG(info) << " Entry end: " << portal->get_entry_end(sector->get_id())); + log::log(MSG(info) << " Connected portals: " << portal->get_connected(sector->get_id()).size()); } } } diff --git a/libopenage/pathfinding/portal.cpp b/libopenage/pathfinding/portal.cpp index 72d4a0ee55..142780843b 100644 --- a/libopenage/pathfinding/portal.cpp +++ b/libopenage/pathfinding/portal.cpp @@ -27,6 +27,15 @@ portal_id_t Portal::get_id() const { return this->id; } +const std::vector> &Portal::get_connected(sector_id_t sector) const { + ENSURE(sector == this->sector0 || sector == this->sector1, "Portal does not connect to sector"); + + if (sector == this->sector0) { + return this->sector0_exits; + } + return this->sector1_exits; +} + const std::vector> &Portal::get_exits(sector_id_t entry_sector) const { ENSURE(entry_sector == this->sector0 || entry_sector == this->sector1, "Invalid entry sector"); diff --git a/libopenage/pathfinding/portal.h b/libopenage/pathfinding/portal.h index 4855d0eb89..509ef0b318 100644 --- a/libopenage/pathfinding/portal.h +++ b/libopenage/pathfinding/portal.h @@ -70,6 +70,15 @@ class Portal { */ portal_id_t get_id() const; + /** + * Get the connected portals in the specified sector. + * + * @param sector Sector ID. + * + * @return Connected portals in the sector. + */ + const std::vector> &get_connected(sector_id_t sector) const; + /** * Get the exit portals reachable via the portal when entering from a specified sector. * diff --git a/libopenage/pathfinding/sector.cpp b/libopenage/pathfinding/sector.cpp index 0b826ce16c..14c0ddeb36 100644 --- a/libopenage/pathfinding/sector.cpp +++ b/libopenage/pathfinding/sector.cpp @@ -2,6 +2,9 @@ #include "sector.h" +#include +#include + #include "error/error.h" #include "coord/tile.h" @@ -127,8 +130,129 @@ std::vector> Sector::find_portals(const std::shared_ptr< return result; } -void Sector::connect_portals() { - // TODO: Flood fill to find connected sectors +void Sector::connect_exits() { + if (this->portals.empty()) { + return; + } + + std::unordered_set portal_ids; + for (const auto &portal : this->portals) { + portal_ids.insert(portal->get_id()); + } + + // check all portals in the sector + std::vector> search_portals = this->portals; + while (not portal_ids.empty()) { + auto portal = search_portals.back(); + search_portals.pop_back(); + portal_ids.erase(portal->get_id()); + + auto start = portal->get_entry_start(this->id); + auto end = portal->get_entry_end(this->id); + + std::unordered_set visited; + std::deque open_list; + std::vector neighbors; + neighbors.reserve(4); + + if (portal->get_direction() == PortalDirection::NORTH_SOUTH) { + // right edge; top to bottom + for (size_t i = start.se; i <= end.se; ++i) { + open_list.push_back(start.ne + i * this->cost_field->get_size()); + } + } + else if (portal->get_direction() == PortalDirection::EAST_WEST) { + // bottom edge; east to west + for (size_t i = start.ne; i <= end.ne; ++i) { + open_list.push_back(i + start.se * this->cost_field->get_size()); + } + } + + // flood fill the grid to find connected portals + while (not open_list.empty()) { + auto current = open_list.front(); + open_list.pop_front(); + + if (visited.contains(current)) { + continue; + } + + // Get the x and y coordinates of the current cell + auto x = current % this->cost_field->get_size(); + auto y = current / this->cost_field->get_size(); + + // check the neighbors + if (y > 0) { + neighbors.push_back(current - this->cost_field->get_size()); + } + if (x > 0) { + neighbors.push_back(current - 1); + } + if (y < this->cost_field->get_size() - 1) { + neighbors.push_back(current + this->cost_field->get_size()); + } + if (x < this->cost_field->get_size() - 1) { + neighbors.push_back(current + 1); + } + + // add the neighbors to the open list + for (const auto &neighbor : neighbors) { + if (this->cost_field->get_cost(neighbor) != COST_IMPASSABLE) { + open_list.push_back(neighbor); + } + } + neighbors.clear(); + + // mark the current cell as visited + visited.insert(current); + } + + // check if the visited cells are connected to another portal + std::vector> connected_portals; + for (auto &exit : this->portals) { + if (exit->get_id() == portal->get_id()) { + // skip the current portal + continue; + } + + // get the start cell of the exit portal + // we only have to check one cell since the flood fill + // should reach any exit cell + auto exit_start = exit->get_entry_start(this->id); + auto exit_cell = exit_start.ne + exit_start.se * this->cost_field->get_size(); + + // check if the exit cell is connected to the visited cells + if (visited.contains(exit_cell)) { + connected_portals.push_back(exit); + } + } + + // set the exits for the current portal + portal->set_exits(this->id, connected_portals); + + // All connected portals share the same exits + // so we can connect them here + for (auto &connected : connected_portals) { + // make a new vector with all connected portals except the current one + std::vector> other_connected; + for (auto &other : connected_portals) { + if (other->get_id() != connected->get_id()) { + other_connected.push_back(other); + } + } + + // add the original portal as it is not in the connected portals vector + other_connected.push_back(portal); + + // set the exits for the connected portal + connected->set_exits(this->id, other_connected); + + // we don't need to food fill for this portal since we have + // found all exits, so we can remove it from the portals that + // should be searched + portal_ids.erase(connected->get_id()); + } + } } } // namespace openage::path diff --git a/libopenage/pathfinding/sector.h b/libopenage/pathfinding/sector.h index 66c2090ec9..84f4bea0c4 100644 --- a/libopenage/pathfinding/sector.h +++ b/libopenage/pathfinding/sector.h @@ -89,7 +89,7 @@ class Sector { * This method should be called after all sectors and portals have * been created and initialized. */ - void connect_portals(); + void connect_exits(); private: /** From 9a1cc15969f032756863944e031a039d8e346fd0 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 24 Mar 2024 22:42:37 +0100 Subject: [PATCH 062/112] path: Refactor demo 1 code for readability. --- libopenage/pathfinding/demo/demo_1.cpp | 3 +- libopenage/pathfinding/demo/demo_1.h | 240 ++++++++++++------------- 2 files changed, 121 insertions(+), 122 deletions(-) diff --git a/libopenage/pathfinding/demo/demo_1.cpp b/libopenage/pathfinding/demo/demo_1.cpp index 3954ba80b8..44cbfd906a 100644 --- a/libopenage/pathfinding/demo/demo_1.cpp +++ b/libopenage/pathfinding/demo/demo_1.cpp @@ -48,11 +48,10 @@ void path_demo_1(const util::Path &path) { } } + // Connect portals inside sectors. for (auto sector : grid->get_sectors()) { sector->connect_exits(); - } - for (auto sector : grid->get_sectors()) { log::log(MSG(info) << "Sector " << sector->get_id() << " has " << sector->get_portals().size() << " portals."); for (auto portal : sector->get_portals()) { log::log(MSG(info) << " Portal " << portal->get_id() << ":"); diff --git a/libopenage/pathfinding/demo/demo_1.h b/libopenage/pathfinding/demo/demo_1.h index e74f79e487..933d12e7c3 100644 --- a/libopenage/pathfinding/demo/demo_1.h +++ b/libopenage/pathfinding/demo/demo_1.h @@ -27,170 +27,170 @@ void path_demo_1(const util::Path &path); const std::vector> sectors_cost = { { // clang-format off - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 255, 255, 255, 255, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 255, 255, 255, 255, 1, 1, - 1, 1, 1, 255, 255, 255, 255, 255, 1, 1, - 1, 1, 255, 255, 255, 255, 255, 1, 1, 1, - 1, 1, 255, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 255, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 255, 255, 255, 255, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 255, 255, 255, 255, 1, 1, + 1, 1, 1, 255, 255, 255, 255, 255, 1, 1, + 1, 1, 255, 255, 255, 255, 255, 1, 1, 1, + 1, 1, 255, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 255, 1, 1, 1, 1, 1, 1, 1, // clang-format on }, { // clang-format off - 1, 1, 255, 255, 255, 255, 255, 255, 255, 255, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 255, 1, 1, 255, 1, 1, 1, - 1, 1, 1, 255, 1, 255, 255, 1, 1, 1, - 1, 1, 1, 255, 255, 255, 255, 255, 255, 255, - 1, 1, 1, 1, 255, 255, 255, 1, 1, 1, - 1, 1, 1, 1, 255, 255, 1, 1, 1, 1, - 1, 1, 1, 255, 1, 1, 1, 1, 1, 1, + 1, 1, 255, 255, 255, 255, 255, 255, 255, 255, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 255, 1, 1, 255, 1, 1, 1, + 1, 1, 1, 255, 1, 255, 255, 1, 1, 1, + 1, 1, 1, 255, 255, 255, 255, 255, 255, 255, + 1, 1, 1, 1, 255, 255, 255, 1, 1, 1, + 1, 1, 1, 1, 255, 255, 1, 1, 1, 1, + 1, 1, 1, 255, 1, 1, 1, 1, 1, 1, // clang-format on }, { // clang-format off - 255, 255, 1, 1, 1, 1, 1, 255, 255, 255, - 1, 1, 1, 1, 1, 1, 1, 1, 255, 255, - 1, 1, 1, 1, 1, 1, 1, 1, 255, 255, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 255, 255, 255, 1, 1, 1, - 1, 1, 1, 1, 1, 255, 255, 255, 255, 255, - 1, 1, 1, 1, 255, 255, 255, 255, 255, 1, - 1, 1, 1, 1, 255, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 255, 1, 1, 1, 1, 255, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 255, 255, 1, 1, 1, 1, 1, 255, 255, 255, + 1, 1, 1, 1, 1, 1, 1, 1, 255, 255, + 1, 1, 1, 1, 1, 1, 1, 1, 255, 255, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 255, 255, 255, 1, 1, 1, + 1, 1, 1, 1, 1, 255, 255, 255, 255, 255, + 1, 1, 1, 1, 255, 255, 255, 255, 255, 1, + 1, 1, 1, 1, 255, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 255, 1, 1, 1, 1, 255, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // clang-format on }, { // clang-format off - 255, 255, 1, 1, 1, 1, 1, 1, 1, 1, - 255, 255, 1, 1, 1, 1, 1, 1, 1, 1, - 255, 255, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 255, 1, 1, 1, 1, 1, 1, - 1, 1, 255, 255, 1, 1, 1, 1, 1, 1, - 255, 255, 255, 255, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 255, 255, 1, 1, 1, 1, 1, 1, 1, 1, + 255, 255, 1, 1, 1, 1, 1, 1, 1, 1, + 255, 255, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 255, 1, 1, 1, 1, 1, 1, + 1, 1, 255, 255, 1, 1, 1, 1, 1, 1, + 255, 255, 255, 255, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // clang-format on }, { // clang-format off - 1, 1, 255, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 255, 1, 1, 1, 1, 1, 255, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 255, 1, 1, 1, 1, 1, - 1, 1, 1, 255, 255, 1, 1, 1, 1, 1, - 1, 1, 255, 1, 255, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 255, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 255, 255, 255, 255, 255, 255, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 255, 255, 255, + 1, 1, 255, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 255, 1, 1, 1, 1, 1, 255, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 255, 1, 1, 1, 1, 1, + 1, 1, 1, 255, 255, 1, 1, 1, 1, 1, + 1, 1, 255, 1, 255, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 255, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 255, 255, 255, 255, 255, 255, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 255, 255, 255, // clang-format on }, { // clang-format off - 1, 1, 1, 255, 1, 1, 1, 1, 1, 255, - 1, 1, 1, 255, 1, 255, 255, 255, 255, 255, - 1, 1, 1, 255, 1, 1, 1, 1, 255, 255, - 1, 1, 1, 255, 1, 1, 1, 1, 255, 255, - 1, 255, 255, 1, 1, 1, 1, 1, 255, 255, - 1, 1, 255, 1, 1, 255, 1, 1, 1, 1, - 1, 1, 255, 1, 1, 255, 1, 1, 1, 1, - 255, 255, 255, 255, 255, 255, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 255, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 255, 1, 1, 1, 1, 1, 255, + 1, 1, 1, 255, 1, 255, 255, 255, 255, 255, + 1, 1, 1, 255, 1, 1, 1, 1, 255, 255, + 1, 1, 1, 255, 1, 1, 1, 1, 255, 255, + 1, 255, 255, 1, 1, 1, 1, 1, 255, 255, + 1, 1, 255, 1, 1, 255, 1, 1, 1, 1, + 1, 1, 255, 1, 1, 255, 1, 1, 1, 1, + 255, 255, 255, 255, 255, 255, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 255, 1, 1, 1, 1, 1, 1, 1, 1, 1, // clang-format on }, { // clang-format off - 255, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 255, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 255, 1, 1, 1, 1, 1, 1, 1, 255, 255, - 255, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 255, 1, 1, 1, 1, 1, 255, 1, 1, 1, - 255, 1, 1, 1, 1, 1, 255, 1, 1, 1, - 255, 1, 1, 1, 1, 1, 255, 1, 1, 1, - 255, 1, 1, 1, 1, 1, 255, 255, 255, 255, - 255, 1, 1, 1, 255, 255, 255, 255, 255, 1, - 255, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 255, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 255, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 255, 1, 1, 1, 1, 1, 1, 1, 255, 255, + 255, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 255, 1, 1, 1, 1, 1, 255, 1, 1, 1, + 255, 1, 1, 1, 1, 1, 255, 1, 1, 1, + 255, 1, 1, 1, 1, 1, 255, 1, 1, 1, + 255, 1, 1, 1, 1, 1, 255, 255, 255, 255, + 255, 1, 1, 1, 255, 255, 255, 255, 255, 1, + 255, 1, 1, 1, 1, 1, 1, 1, 1, 1, // clang-format on }, { // clang-format off - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 255, 255, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 255, 255, 255, 255, 255, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 255, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 255, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 255, 255, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 255, 255, 255, 255, 255, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 255, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 255, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // clang-format on }, { // clang-format off - 1, 1, 255, 1, 1, 1, 1, 255, 255, 255, - 1, 1, 255, 1, 1, 1, 255, 255, 1, 1, - 1, 1, 1, 1, 1, 255, 255, 255, 1, 1, - 1, 1, 1, 1, 255, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 255, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 255, - 1, 1, 1, 1, 1, 1, 1, 1, 255, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 255, 1, 1, 1, 1, 255, 255, 255, + 1, 1, 255, 1, 1, 1, 255, 255, 1, 1, + 1, 1, 1, 1, 1, 255, 255, 255, 1, 1, + 1, 1, 1, 1, 255, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 255, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 255, + 1, 1, 1, 1, 1, 1, 1, 1, 255, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // clang-format on }, { // clang-format off - 255, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 255, 1, 1, 1, 1, 1, 1, - 1, 1, 255, 255, 255, 255, 255, 255, 1, 1, - 1, 255, 255, 255, 1, 1, 1, 1, 1, 1, - 255, 255, 255, 255, 1, 1, 1, 1, 1, 1, - 255, 1, 1, 1, 1, 1, 1, 1, 1, 255, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 255, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 255, 1, 1, 1, 1, 1, 1, + 1, 1, 255, 255, 255, 255, 255, 255, 1, 1, + 1, 255, 255, 255, 1, 1, 1, 1, 1, 1, + 255, 255, 255, 255, 1, 1, 1, 1, 1, 1, + 255, 1, 1, 1, 1, 1, 1, 1, 1, 255, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // clang-format on }, { // clang-format off - 255, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 255, 255, 255, 255, 255, 1, 1, - 1, 1, 255, 255, 1, 1, 1, 255, 1, 1, - 1, 255, 255, 1, 1, 1, 1, 1, 1, 1, - 255, 255, 1, 1, 1, 1, 1, 1, 1, 1, - 255, 255, 1, 1, 1, 255, 255, 255, 1, 1, - 1, 1, 1, 1, 1, 1, 255, 1, 1, 1, - 1, 1, 1, 255, 255, 255, 255, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 255, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 255, 255, 255, 255, 255, 1, 1, + 1, 1, 255, 255, 1, 1, 1, 255, 1, 1, + 1, 255, 255, 1, 1, 1, 1, 1, 1, 1, + 255, 255, 1, 1, 1, 1, 1, 1, 1, 1, + 255, 255, 1, 1, 1, 255, 255, 255, 1, 1, + 1, 1, 1, 1, 1, 1, 255, 1, 1, 1, + 1, 1, 1, 255, 255, 255, 255, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // clang-format on }, { // clang-format off - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // clang-format on }}; From 2dd0c3aca4d985eb89647c7b282770d49f83b451 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 24 Mar 2024 22:43:39 +0100 Subject: [PATCH 063/112] fix copyright years --- libopenage/coord/tile.h | 2 +- libopenage/pathfinding/legacy/path_utils.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libopenage/coord/tile.h b/libopenage/coord/tile.h index cf020823a7..e8e61a09c2 100644 --- a/libopenage/coord/tile.h +++ b/libopenage/coord/tile.h @@ -1,4 +1,4 @@ -// Copyright 2016-2023 the openage authors. See copying.md for legal info. +// Copyright 2016-2024 the openage authors. See copying.md for legal info. #pragma once diff --git a/libopenage/pathfinding/legacy/path_utils.h b/libopenage/pathfinding/legacy/path_utils.h index c4f4b6fcca..de1aa99da8 100644 --- a/libopenage/pathfinding/legacy/path_utils.h +++ b/libopenage/pathfinding/legacy/path_utils.h @@ -1,4 +1,4 @@ -// Copyright 2014-2016 the openage authors. See copying.md for legal info. +// Copyright 2014-2024 the openage authors. See copying.md for legal info. #pragma once From bf6136bbef22251a964d2a6ad8e8029c26234cc3 Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 25 Mar 2024 03:01:13 +0100 Subject: [PATCH 064/112] path: Get sector size of grid. --- libopenage/pathfinding/grid.cpp | 10 +++++++++- libopenage/pathfinding/grid.h | 12 ++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/libopenage/pathfinding/grid.cpp b/libopenage/pathfinding/grid.cpp index d0ee691774..182bd71fc6 100644 --- a/libopenage/pathfinding/grid.cpp +++ b/libopenage/pathfinding/grid.cpp @@ -4,6 +4,7 @@ #include "error/error.h" +#include "pathfinding/cost_field.h" #include "pathfinding/sector.h" @@ -13,7 +14,8 @@ Grid::Grid(grid_id_t id, const util::Vector2s &size, size_t sector_size) : id{id}, - size{size} { + size{size}, + sector_size{sector_size} { for (size_t y = 0; y < size[1]; y++) { for (size_t x = 0; x < size[0]; x++) { this->sectors.push_back(std::make_shared(x + y * this->size[0], sector_size)); @@ -30,6 +32,8 @@ Grid::Grid(grid_id_t id, ENSURE(this->sectors.size() == size[0] * size[1], "Grid has size " << size[0] << "x" << size[1] << " (" << size[0] * size[1] << " sectors), " << "but only " << this->sectors.size() << " sectors were provided"); + + this->sector_size = sectors.at(0)->get_cost_field()->get_size(); } grid_id_t Grid::get_id() const { @@ -40,6 +44,10 @@ const util::Vector2s &Grid::get_size() const { return this->size; } +size_t Grid::get_sector_size() const { + return this->sector_size; +} + const std::shared_ptr &Grid::get_sector(size_t x, size_t y) { return this->sectors.at(x + y * this->size[0]); } diff --git a/libopenage/pathfinding/grid.h b/libopenage/pathfinding/grid.h index 4cd2a97b7b..c5e783ddfa 100644 --- a/libopenage/pathfinding/grid.h +++ b/libopenage/pathfinding/grid.h @@ -55,6 +55,13 @@ class Grid { */ const util::Vector2s &get_size() const; + /** + * Get the side length of the sectors on the grid. + * + * @return Sector side length. + */ + size_t get_sector_size() const; + /** * Get the sector at a specified position. * @@ -92,6 +99,11 @@ class Grid { */ util::Vector2s size; + /** + * Side length of the grid sectors. + */ + size_t sector_size; + /** * Sectors of the grid. */ From 87bf80529b0a2c01636aadb063de62cde257fc3b Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 25 Mar 2024 03:02:22 +0100 Subject: [PATCH 065/112] path: Append demo ID to RenderManager class name. --- libopenage/pathfinding/demo/demo_0.cpp | 60 +++++++++++++------------- libopenage/pathfinding/demo/demo_0.h | 11 ++--- 2 files changed, 36 insertions(+), 35 deletions(-) diff --git a/libopenage/pathfinding/demo/demo_0.cpp b/libopenage/pathfinding/demo/demo_0.cpp index badbfef0d6..23a5f12d46 100644 --- a/libopenage/pathfinding/demo/demo_0.cpp +++ b/libopenage/pathfinding/demo/demo_0.cpp @@ -60,12 +60,12 @@ void path_demo_0(const util::Path &path) { // Render the grid and the pathfinding results auto qtapp = std::make_shared(); auto window = std::make_shared("openage pathfinding test", 1440, 720); - auto render_manager = std::make_shared(qtapp, window, path); - log::log(INFO << "Created RenderManager for pathfinding demo"); + auto render_manager = std::make_shared(qtapp, window, path); + log::log(INFO << "Created render manager for pathfinding demo"); // Show the cost field on startup render_manager->show_cost_field(cost_field); - auto current_field = RenderManager::field_t::COST; + auto current_field = RenderManager0::field_t::COST; log::log(INFO << "Showing cost field"); // Make steering vector visibility toggleable @@ -95,13 +95,13 @@ void path_demo_0(const util::Path &path) { // Show the new field values and vectors switch (current_field) { - case RenderManager::field_t::COST: + case RenderManager0::field_t::COST: render_manager->show_cost_field(cost_field); break; - case RenderManager::field_t::INTEGRATION: + case RenderManager0::field_t::INTEGRATION: render_manager->show_integration_field(integration_field); break; - case RenderManager::field_t::FLOW: + case RenderManager0::field_t::FLOW: render_manager->show_flow_field(flow_field); break; } @@ -119,17 +119,17 @@ void path_demo_0(const util::Path &path) { if (ev.type() == QEvent::KeyRelease) { if (ev.key() == Qt::Key_F1) { // Show cost field render_manager->show_cost_field(cost_field); - current_field = RenderManager::field_t::COST; + current_field = RenderManager0::field_t::COST; log::log(INFO << "Showing cost field"); } else if (ev.key() == Qt::Key_F2) { // Show integration field render_manager->show_integration_field(integration_field); - current_field = RenderManager::field_t::INTEGRATION; + current_field = RenderManager0::field_t::INTEGRATION; log::log(INFO << "Showing integration field"); } else if (ev.key() == Qt::Key_F3) { // Show flow field render_manager->show_flow_field(flow_field); - current_field = RenderManager::field_t::FLOW; + current_field = RenderManager0::field_t::FLOW; log::log(INFO << "Showing flow field"); } else if (ev.key() == Qt::Key_F4) { // Show steering vectors @@ -156,9 +156,9 @@ void path_demo_0(const util::Path &path) { } -RenderManager::RenderManager(const std::shared_ptr &app, - const std::shared_ptr &window, - const util::Path &path) : +RenderManager0::RenderManager0(const std::shared_ptr &app, + const std::shared_ptr &window, + const util::Path &path) : path{path}, app{app}, window{window}, @@ -175,7 +175,7 @@ RenderManager::RenderManager(const std::shared_ptrwindow->should_close()) { this->app->process_events(); @@ -192,7 +192,7 @@ void RenderManager::run() { this->window->close(); } -void RenderManager::show_cost_field(const std::shared_ptr &field) { +void RenderManager0::show_cost_field(const std::shared_ptr &field) { Eigen::Matrix4f model = Eigen::Matrix4f::Identity(); auto unifs = this->cost_shader->new_uniform_input( "model", @@ -201,7 +201,7 @@ void RenderManager::show_cost_field(const std::shared_ptr &fiel this->camera->get_view_matrix(), "proj", this->camera->get_projection_matrix()); - auto mesh = RenderManager::get_cost_field_mesh(field); + auto mesh = RenderManager0::get_cost_field_mesh(field); auto geometry = this->renderer->add_mesh_geometry(mesh); renderer::Renderable renderable{ unifs, @@ -212,7 +212,7 @@ void RenderManager::show_cost_field(const std::shared_ptr &fiel this->field_pass->set_renderables({renderable}); } -void RenderManager::show_integration_field(const std::shared_ptr &field) { +void RenderManager0::show_integration_field(const std::shared_ptr &field) { Eigen::Matrix4f model = Eigen::Matrix4f::Identity(); auto unifs = this->integration_shader->new_uniform_input( "model", @@ -232,7 +232,7 @@ void RenderManager::show_integration_field(const std::shared_ptrfield_pass->set_renderables({renderable}); } -void RenderManager::show_flow_field(const std::shared_ptr &field) { +void RenderManager0::show_flow_field(const std::shared_ptr &field) { Eigen::Matrix4f model = Eigen::Matrix4f::Identity(); auto unifs = this->flow_shader->new_uniform_input( "model", @@ -252,7 +252,7 @@ void RenderManager::show_flow_field(const std::shared_ptr &fiel this->field_pass->set_renderables({renderable}); } -void RenderManager::show_vectors(const std::shared_ptr &field) { +void RenderManager0::show_vectors(const std::shared_ptr &field) { static const std::unordered_map offset_dir{ {flow_dir_t::NORTH, {-0.25f, 0.0f, 0.0f}}, {flow_dir_t::NORTH_EAST, {-0.25f, 0.0f, -0.25f}}, @@ -299,11 +299,11 @@ void RenderManager::show_vectors(const std::shared_ptr &field) } } -void RenderManager::hide_vectors() { +void RenderManager0::hide_vectors() { this->vector_pass->clear_renderables(); } -std::pair RenderManager::select_tile(double x, double y) { +std::pair RenderManager0::select_tile(double x, double y) { auto grid_plane_normal = Eigen::Vector3f{0, 1, 0}; auto grid_plane_point = Eigen::Vector3f{0, 0, 0}; auto camera_direction = renderer::camera::cam_direction; @@ -317,7 +317,7 @@ std::pair RenderManager::select_tile(double x, double y) { return {grid_x, grid_y}; } -void RenderManager::init_shaders() { +void RenderManager0::init_shaders() { // Shader sources auto shaderdir = this->path / "assets" / "test" / "shaders" / "pathfinding"; @@ -422,7 +422,7 @@ void RenderManager::init_shaders() { this->display_shader = renderer->add_shader({display_vshader_src, display_fshader_src}); } -void RenderManager::init_passes() { +void RenderManager0::init_passes() { auto size = this->window->get_size(); // Make a framebuffer for the background render pass to draw into @@ -539,8 +539,8 @@ void RenderManager::init_passes() { renderer->get_display_target()); } -renderer::resources::MeshData RenderManager::get_cost_field_mesh(const std::shared_ptr &field, - size_t resolution) { +renderer::resources::MeshData RenderManager0::get_cost_field_mesh(const std::shared_ptr &field, + size_t resolution) { // increase by 1 in every dimension because to get the vertex length // of each dimension util::Vector2s size{ @@ -625,8 +625,8 @@ renderer::resources::MeshData RenderManager::get_cost_field_mesh(const std::shar return {std::move(vert_data), std::move(idx_data), info}; } -renderer::resources::MeshData RenderManager::get_integration_field_mesh(const std::shared_ptr &field, - size_t resolution) { +renderer::resources::MeshData RenderManager0::get_integration_field_mesh(const std::shared_ptr &field, + size_t resolution) { // increase by 1 in every dimension because to get the vertex length // of each dimension util::Vector2s size{ @@ -711,8 +711,8 @@ renderer::resources::MeshData RenderManager::get_integration_field_mesh(const st return {std::move(vert_data), std::move(idx_data), info}; } -renderer::resources::MeshData RenderManager::get_flow_field_mesh(const std::shared_ptr &field, - size_t resolution) { +renderer::resources::MeshData RenderManager0::get_flow_field_mesh(const std::shared_ptr &field, + size_t resolution) { // increase by 1 in every dimension because to get the vertex length // of each dimension util::Vector2s size{ @@ -797,7 +797,7 @@ renderer::resources::MeshData RenderManager::get_flow_field_mesh(const std::shar return {std::move(vert_data), std::move(idx_data), info}; } -renderer::resources::MeshData RenderManager::get_arrow_mesh() { +renderer::resources::MeshData RenderManager0::get_arrow_mesh() { // vertices for the arrow // x, y, z std::vector verts{ @@ -821,7 +821,7 @@ renderer::resources::MeshData RenderManager::get_arrow_mesh() { return {std::move(vert_data), info}; } -renderer::resources::MeshData RenderManager::get_grid_mesh(size_t side_length) { +renderer::resources::MeshData RenderManager0::get_grid_mesh(size_t side_length) { // increase by 1 in every dimension because to get the vertex length // of each dimension util::Vector2s size{side_length + 1, side_length + 1}; diff --git a/libopenage/pathfinding/demo/demo_0.h b/libopenage/pathfinding/demo/demo_0.h index f84472ee54..7a07ebfd7e 100644 --- a/libopenage/pathfinding/demo/demo_0.h +++ b/libopenage/pathfinding/demo/demo_0.h @@ -50,7 +50,7 @@ void path_demo_0(const util::Path &path); /** * Manages the graphical display of the pathfinding demo. */ -class RenderManager { +class RenderManager0 { public: enum class field_t { COST, @@ -65,10 +65,10 @@ class RenderManager { * @param window Window to render to. * @param path Path to the project rootdir. */ - RenderManager(const std::shared_ptr &app, - const std::shared_ptr &window, - const util::Path &path); - ~RenderManager() = default; + RenderManager0(const std::shared_ptr &app, + const std::shared_ptr &window, + const util::Path &path); + ~RenderManager0() = default; /** * Run the render loop. @@ -126,6 +126,7 @@ class RenderManager { * Create the following render passes for the demo: * - Background pass: Mono-colored background object. * - Field pass; Renders the cost, integration and flow fields. + * - Vector pass: Renders the steering vectors of a flow field. * - Grid pass: Renders a grid on top of the fields. * - Display pass: Draws the results of previous passes to the screen. */ From 8ab759d700f7f0299cb1b385660d6d38157a2984 Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 25 Mar 2024 23:19:20 +0100 Subject: [PATCH 066/112] path: Draw grid in demo 1. --- .../pathfinding/demo_1_display.frag.glsl | 10 + .../pathfinding/demo_1_display.vert.glsl | 10 + .../shaders/pathfinding/demo_1_grid.frag.glsl | 8 + .../shaders/pathfinding/demo_1_grid.vert.glsl | 11 + .../shaders/pathfinding/demo_1_obj.frag.glsl | 9 + .../shaders/pathfinding/demo_1_obj.vert.glsl | 11 + libopenage/pathfinding/demo/demo_0.cpp | 2 +- libopenage/pathfinding/demo/demo_1.cpp | 322 ++++++++++++++++++ libopenage/pathfinding/demo/demo_1.h | 146 +++++++- 9 files changed, 526 insertions(+), 3 deletions(-) create mode 100644 assets/test/shaders/pathfinding/demo_1_display.frag.glsl create mode 100644 assets/test/shaders/pathfinding/demo_1_display.vert.glsl create mode 100644 assets/test/shaders/pathfinding/demo_1_grid.frag.glsl create mode 100644 assets/test/shaders/pathfinding/demo_1_grid.vert.glsl create mode 100644 assets/test/shaders/pathfinding/demo_1_obj.frag.glsl create mode 100644 assets/test/shaders/pathfinding/demo_1_obj.vert.glsl diff --git a/assets/test/shaders/pathfinding/demo_1_display.frag.glsl b/assets/test/shaders/pathfinding/demo_1_display.frag.glsl new file mode 100644 index 0000000000..a6732d0c7f --- /dev/null +++ b/assets/test/shaders/pathfinding/demo_1_display.frag.glsl @@ -0,0 +1,10 @@ +#version 330 + +uniform sampler2D color_texture; + +in vec2 v_uv; +out vec4 col; + +void main() { + col = texture(color_texture, v_uv); +} diff --git a/assets/test/shaders/pathfinding/demo_1_display.vert.glsl b/assets/test/shaders/pathfinding/demo_1_display.vert.glsl new file mode 100644 index 0000000000..6112530242 --- /dev/null +++ b/assets/test/shaders/pathfinding/demo_1_display.vert.glsl @@ -0,0 +1,10 @@ +#version 330 + +layout(location=0) in vec2 position; +layout(location=1) in vec2 uv; +out vec2 v_uv; + +void main() { + gl_Position = vec4(position, 0.0, 1.0); + v_uv = uv; +} diff --git a/assets/test/shaders/pathfinding/demo_1_grid.frag.glsl b/assets/test/shaders/pathfinding/demo_1_grid.frag.glsl new file mode 100644 index 0000000000..19d2e459c3 --- /dev/null +++ b/assets/test/shaders/pathfinding/demo_1_grid.frag.glsl @@ -0,0 +1,8 @@ +#version 330 + +out vec4 out_col; + +void main() +{ + out_col = vec4(0.0, 0.0, 0.0, 0.3); +} diff --git a/assets/test/shaders/pathfinding/demo_1_grid.vert.glsl b/assets/test/shaders/pathfinding/demo_1_grid.vert.glsl new file mode 100644 index 0000000000..cbf6712eef --- /dev/null +++ b/assets/test/shaders/pathfinding/demo_1_grid.vert.glsl @@ -0,0 +1,11 @@ +#version 330 + +layout (location = 0) in vec2 position; + +uniform mat4 model; +uniform mat4 view; +uniform mat4 proj; + +void main() { + gl_Position = proj * view * model * vec4(position, 0.0, 1.0); +} diff --git a/assets/test/shaders/pathfinding/demo_1_obj.frag.glsl b/assets/test/shaders/pathfinding/demo_1_obj.frag.glsl new file mode 100644 index 0000000000..ebbb10bc8f --- /dev/null +++ b/assets/test/shaders/pathfinding/demo_1_obj.frag.glsl @@ -0,0 +1,9 @@ +#version 330 + +uniform vec4 color; + +out vec4 outcol; + +void main() { + outcol = color; +} diff --git a/assets/test/shaders/pathfinding/demo_1_obj.vert.glsl b/assets/test/shaders/pathfinding/demo_1_obj.vert.glsl new file mode 100644 index 0000000000..bf804ffe53 --- /dev/null +++ b/assets/test/shaders/pathfinding/demo_1_obj.vert.glsl @@ -0,0 +1,11 @@ +#version 330 + +layout(location=0) in vec2 position; + +uniform mat4 model; +uniform mat4 view; +uniform mat4 proj; + +void main() { + gl_Position = proj * view * model * vec4(position, 0.0, 1.0); +} diff --git a/libopenage/pathfinding/demo/demo_0.cpp b/libopenage/pathfinding/demo/demo_0.cpp index 23a5f12d46..e87b44d1f0 100644 --- a/libopenage/pathfinding/demo/demo_0.cpp +++ b/libopenage/pathfinding/demo/demo_0.cpp @@ -493,7 +493,7 @@ void RenderManager0::init_passes() { camera->get_view_matrix(), "proj", camera->get_projection_matrix()); - auto grid_mesh = get_grid_mesh(10); + auto grid_mesh = this->get_grid_mesh(10); auto grid_geometry = renderer->add_mesh_geometry(grid_mesh); renderer::Renderable grid_obj{ grid_unifs, diff --git a/libopenage/pathfinding/demo/demo_1.cpp b/libopenage/pathfinding/demo/demo_1.cpp index 44cbfd906a..c25bda4390 100644 --- a/libopenage/pathfinding/demo/demo_1.cpp +++ b/libopenage/pathfinding/demo/demo_1.cpp @@ -7,6 +7,13 @@ #include "pathfinding/portal.h" #include "pathfinding/sector.h" +#include "renderer/gui/integration/public/gui_application_with_logger.h" +#include "renderer/opengl/window.h" +#include "renderer/resources/shader_source.h" +#include "renderer/resources/texture_info.h" +#include "renderer/shader_program.h" +#include "renderer/window.h" + namespace openage::path::tests { @@ -62,5 +69,320 @@ void path_demo_1(const util::Path &path) { log::log(MSG(info) << " Connected portals: " << portal->get_connected(sector->get_id()).size()); } } + + // Render the grid + auto qtapp = std::make_shared(); + auto window = std::make_shared("openage pathfinding test", 1024, 768); + auto render_manager = std::make_shared(qtapp, window, path, grid); + log::log(INFO << "Created render manager for pathfinding demo"); + + render_manager->run(); +} + + +RenderManager1::RenderManager1(const std::shared_ptr &app, + const std::shared_ptr &window, + const util::Path &path, + const std::shared_ptr &grid) : + path{path}, + grid{grid}, + app{app}, + window{window}, + renderer{window->make_renderer()} { + this->init_shaders(); + this->init_passes(); +} + +void RenderManager1::run() { + while (not this->window->should_close()) { + this->app->process_events(); + + this->renderer->render(this->background_pass); + this->renderer->render(this->field_pass); + this->renderer->render(this->display_pass); + + this->window->update(); + + this->renderer->check_error(); + } + this->window->close(); +} + +void RenderManager1::init_passes() { + auto size = this->window->get_size(); + + // Make a framebuffer for the background render pass to draw into + auto background_texture = renderer->add_texture( + renderer::resources::Texture2dInfo(size[0], + size[1], + renderer::resources::pixel_format::rgba8)); + auto depth_texture = renderer->add_texture( + renderer::resources::Texture2dInfo(size[0], + size[1], + renderer::resources::pixel_format::depth24)); + auto fbo = renderer->create_texture_target({background_texture, depth_texture}); + + // Background object for contrast between field and display + Eigen::Matrix4f id_matrix = Eigen::Matrix4f::Identity(); + auto background_unifs = obj_shader->new_uniform_input( + "color", + Eigen::Vector4f{1.0, 1.0, 1.0, 1.0}, + "model", + id_matrix, + "view", + id_matrix, + "proj", + id_matrix); + auto background = renderer->add_mesh_geometry(renderer::resources::MeshData::make_quad()); + renderer::Renderable background_obj{ + background_unifs, + background, + true, + true, + }; + this->background_pass = renderer->add_render_pass({background_obj}, fbo); + + // Make a framebuffer for the field render pass to draw into + auto field_texture = renderer->add_texture( + renderer::resources::Texture2dInfo(size[0], + size[1], + renderer::resources::pixel_format::rgba8)); + auto depth_texture_2 = renderer->add_texture( + renderer::resources::Texture2dInfo(size[0], + size[1], + renderer::resources::pixel_format::depth24)); + auto field_fbo = renderer->create_texture_target({field_texture, depth_texture_2}); + this->field_pass = renderer->add_render_pass({}, field_fbo); + this->create_impassible_tiles(this->grid); + + // Make a framebuffer for the grid render passes to draw into + auto grid_texture = renderer->add_texture( + renderer::resources::Texture2dInfo(size[0], + size[1], + renderer::resources::pixel_format::rgba8)); + auto depth_texture_3 = renderer->add_texture( + renderer::resources::Texture2dInfo(size[0], + size[1], + renderer::resources::pixel_format::depth24)); + auto grid_fbo = renderer->create_texture_target({grid_texture, depth_texture_3}); + + // Create object for the grid + auto model = Eigen::Affine3f::Identity(); + model.prescale(Eigen::Vector3f{ + 1.0f / (this->grid->get_size()[0] * this->grid->get_sector_size()), + 1.0f / (this->grid->get_size()[1] * this->grid->get_sector_size()), + 1.0f}); + auto grid_unifs = grid_shader->new_uniform_input( + "model", + model.matrix(), + "view", + id_matrix, + "proj", + id_matrix); + auto grid_mesh = this->get_grid_mesh(this->grid); + auto grid_geometry = renderer->add_mesh_geometry(grid_mesh); + renderer::Renderable grid_obj{ + grid_unifs, + grid_geometry, + true, + true, + }; + this->grid_pass = renderer->add_render_pass({grid_obj}, grid_fbo); + + // Make two objects that draw the results of the previous passes onto the screen + // in the display render pass + auto bg_texture_unif = display_shader->new_uniform_input("color_texture", background_texture); + auto quad = renderer->add_mesh_geometry(renderer::resources::MeshData::make_quad()); + renderer::Renderable bg_pass_obj{ + bg_texture_unif, + quad, + true, + true, + }; + auto field_texture_unif = display_shader->new_uniform_input("color_texture", field_texture); + renderer::Renderable field_pass_obj{ + field_texture_unif, + quad, + true, + true, + }; + auto grid_texture_unif = display_shader->new_uniform_input("color_texture", grid_texture); + renderer::Renderable grid_pass_obj{ + grid_texture_unif, + quad, + true, + true, + }; + this->display_pass = renderer->add_render_pass( + {bg_pass_obj, field_pass_obj, grid_pass_obj}, + renderer->get_display_target()); +} + +void RenderManager1::init_shaders() { + // Shader sources + auto shaderdir = this->path / "assets" / "test" / "shaders" / "pathfinding"; + + // Shader for rendering the grid + auto grid_vshader_file = shaderdir / "demo_1_grid.vert.glsl"; + auto grid_vshader_src = renderer::resources::ShaderSource( + renderer::resources::shader_lang_t::glsl, + renderer::resources::shader_stage_t::vertex, + grid_vshader_file); + + auto grid_fshader_file = shaderdir / "demo_1_grid.frag.glsl"; + auto grid_fshader_src = renderer::resources::ShaderSource( + renderer::resources::shader_lang_t::glsl, + renderer::resources::shader_stage_t::fragment, + grid_fshader_file); + + // Shader for 2D monocolored objects + auto obj_vshader_file = shaderdir / "demo_1_obj.vert.glsl"; + auto obj_vshader_src = renderer::resources::ShaderSource( + renderer::resources::shader_lang_t::glsl, + renderer::resources::shader_stage_t::vertex, + obj_vshader_file); + + auto obj_fshader_file = shaderdir / "demo_1_obj.frag.glsl"; + auto obj_fshader_src = renderer::resources::ShaderSource( + renderer::resources::shader_lang_t::glsl, + renderer::resources::shader_stage_t::fragment, + obj_fshader_file); + + // Shader for rendering to the display target + auto display_vshader_file = shaderdir / "demo_1_display.vert.glsl"; + auto display_vshader_src = renderer::resources::ShaderSource( + renderer::resources::shader_lang_t::glsl, + renderer::resources::shader_stage_t::vertex, + display_vshader_file); + + auto display_fshader_file = shaderdir / "demo_1_display.frag.glsl"; + auto display_fshader_src = renderer::resources::ShaderSource( + renderer::resources::shader_lang_t::glsl, + renderer::resources::shader_stage_t::fragment, + display_fshader_file); + + // Create the shaders + this->grid_shader = renderer->add_shader({grid_vshader_src, grid_fshader_src}); + this->obj_shader = renderer->add_shader({obj_vshader_src, obj_fshader_src}); + this->display_shader = renderer->add_shader({display_vshader_src, display_fshader_src}); +} + + +renderer::resources::MeshData RenderManager1::get_grid_mesh(const std::shared_ptr &grid) { + // increase by 1 in every dimension because to get the vertex length + // of each dimension + util::Vector2s size{ + grid->get_size()[0] * grid->get_sector_size() + 1, + grid->get_size()[1] * grid->get_sector_size() + 1}; + + // add vertices for the cells of the grid + std::vector verts{}; + auto vert_count = size[0] * size[1]; + verts.reserve(vert_count * 2); + for (int i = 0; i < (int)size[0]; ++i) { + for (int j = 0; j < (int)size[1]; ++j) { + verts.push_back(i); + verts.push_back(j); + } + } + + // split the grid into lines using an index array + std::vector idxs; + idxs.reserve((size[0] - 1) * (size[1] - 1) * 8); + // iterate over all tiles in the grid by columns, i.e. starting + // from the left corner to the bottom corner if you imagine it from + // the camera's point of view + for (size_t i = 0; i < size[0] - 1; ++i) { + for (size_t j = 0; j < size[1] - 1; ++j) { + // since we are working on tiles, we just draw a square using the 4 vertices + idxs.push_back(j + i * size[1]); // bottom left + idxs.push_back(j + 1 + i * size[1]); // bottom right + idxs.push_back(j + 1 + i * size[1]); // bottom right + idxs.push_back(j + size[1] + 1 + i * size[1]); // top right + idxs.push_back(j + size[1] + 1 + i * size[1]); // top right + idxs.push_back(j + size[1] + i * size[1]); // top left + idxs.push_back(j + size[1] + i * size[1]); // top left + idxs.push_back(j + i * size[1]); // bottom left + } + } + + renderer::resources::VertexInputInfo info{ + {renderer::resources::vertex_input_t::V2F32}, + renderer::resources::vertex_layout_t::AOS, + renderer::resources::vertex_primitive_t::LINES, + renderer::resources::index_t::U16}; + + auto const vert_data_size = verts.size() * sizeof(float); + std::vector vert_data(vert_data_size); + std::memcpy(vert_data.data(), verts.data(), vert_data_size); + + auto const idx_data_size = idxs.size() * sizeof(uint16_t); + std::vector idx_data(idx_data_size); + std::memcpy(idx_data.data(), idxs.data(), idx_data_size); + + return {std::move(vert_data), std::move(idx_data), info}; } + +void RenderManager1::create_impassible_tiles(const std::shared_ptr &grid) { + auto width = grid->get_size()[0]; + auto height = grid->get_size()[1]; + auto sector_size = grid->get_sector_size(); + + float tile_offset_width = 2.0f / (width * sector_size); + float tile_offset_height = 2.0f / (height * sector_size); + + for (size_t sector_x = 0; sector_x < width; sector_x++) { + for (size_t sector_y = 0; sector_y < height; sector_y++) { + auto sector = grid->get_sector(sector_x, sector_y); + auto cost_field = sector->get_cost_field(); + for (size_t y = 0; y < sector_size; y++) { + for (size_t x = 0; x < sector_size; x++) { + auto cost = cost_field->get_cost(coord::tile{x, y}); + if (cost == COST_IMPASSABLE) { + std::array tile_data{ + -1.0f + x * tile_offset_width + sector_size * sector_x * tile_offset_width, + 1.0f - y * tile_offset_height - sector_size * sector_y * tile_offset_height, + -1.0f + x * tile_offset_width + sector_size * sector_x * tile_offset_width, + 1.0f - (y + 1) * tile_offset_height - sector_size * sector_y * tile_offset_height, + -1.0f + (x + 1) * tile_offset_width + sector_size * sector_x * tile_offset_width, + 1.0f - y * tile_offset_height - sector_size * sector_y * tile_offset_height, + -1.0f + (x + 1) * tile_offset_width + sector_size * sector_x * tile_offset_width, + 1.0f - (y + 1) * tile_offset_height - sector_size * sector_y * tile_offset_height, + }; + renderer::resources::VertexInputInfo info{ + {renderer::resources::vertex_input_t::V2F32}, + renderer::resources::vertex_layout_t::AOS, + renderer::resources::vertex_primitive_t::TRIANGLE_STRIP}; + + auto const data_size = tile_data.size() * sizeof(float); + std::vector verts(data_size); + std::memcpy(verts.data(), tile_data.data(), data_size); + + auto tile_mesh = renderer::resources::MeshData(std::move(verts), info); + auto tile_geometry = renderer->add_mesh_geometry(tile_mesh); + + Eigen::Matrix4f id_matrix = Eigen::Matrix4f::Identity(); + auto tile_unifs = obj_shader->new_uniform_input( + "color", + Eigen::Vector4f{0.0, 0.0, 0.0, 1.0}, + "model", + id_matrix, + "view", + id_matrix, + "proj", + id_matrix); + auto tile_obj = renderer::Renderable{ + tile_unifs, + tile_geometry, + true, + true, + }; + this->field_pass->add_renderables({tile_obj}); + } + } + } + } + } +} + } // namespace openage::path::tests diff --git a/libopenage/pathfinding/demo/demo_1.h b/libopenage/pathfinding/demo/demo_1.h index 933d12e7c3..b067ff7553 100644 --- a/libopenage/pathfinding/demo/demo_1.h +++ b/libopenage/pathfinding/demo/demo_1.h @@ -3,10 +3,27 @@ #pragma once #include "pathfinding/definitions.h" +#include "renderer/resources/mesh_data.h" #include "util/path.h" -namespace openage::path::tests { +namespace openage { +namespace renderer { +class Renderer; +class RenderPass; +class ShaderProgram; +class Window; + +namespace gui { +class GuiApplicationWithLogger; +} // namespace gui + +} // namespace renderer + +namespace path { +class Grid; + +namespace tests { /** @@ -22,6 +39,129 @@ namespace openage::path::tests { void path_demo_1(const util::Path &path); +/** + * Manages the graphical display of the pathfinding demo. + */ +class RenderManager1 { +public: + /** + * Create a new render manager. + * + * @param app GUI application. + * @param window Window to render to. + * @param path Path to the project rootdir. + */ + RenderManager1(const std::shared_ptr &app, + const std::shared_ptr &window, + const util::Path &path, + const std::shared_ptr &grid); + ~RenderManager1() = default; + + /** + * Run the render loop. + */ + void run(); + +private: + /** + * Load the shader sources for the demo and create the shader programs. + */ + void init_shaders(); + + /** + * Create the following render passes for the demo: + * - Background pass: Mono-colored background object. + * - Field pass; Renders the cost, integration and flow fields. + * - Grid pass: Renders a grid on top of the cost field. + * - Display pass: Draws the results of previous passes to the screen. + */ + void init_passes(); + + /** + * Create a mesh for the grid. + * + * @param grid Pathing grid. + * + * @return Mesh data for the grid. + */ + static renderer::resources::MeshData get_grid_mesh(const std::shared_ptr &grid); + + /** + * Create objects for the impassible tiles in the grid. + * + * @param grid Pathing grid. + * + * @return Mesh data for the grid. + */ + void create_impassible_tiles(const std::shared_ptr &grid); + + /** + * Path to the project rootdir. + */ + const util::Path &path; + + /** + * Pathing grid of the demo. + */ + std::shared_ptr grid; + + /* Renderer objects */ + + /** + * Qt GUI application. + */ + std::shared_ptr app; + + /** + * openage window to render to. + */ + std::shared_ptr window; + + /** + * openage renderer instance. + */ + std::shared_ptr renderer; + + /* Shader programs */ + + /** + * Shader program for rendering a grid. + */ + std::shared_ptr grid_shader; + + /** + * Shader program for rendering 2D mono-colored objects. + */ + std::shared_ptr obj_shader; + + /** + * Shader program for rendering the final display. + */ + std::shared_ptr display_shader; + + /* Render passes */ + + /** + * Background pass: Mono-colored background object. + */ + std::shared_ptr background_pass; + + /** + * Field pass: Renders the cost field. + */ + std::shared_ptr field_pass; + + /** + * Grid pass: Renders a grid on top of the cost field. + */ + std::shared_ptr grid_pass; + + /** + * Display pass: Draws the results of previous passes to the screen. + */ + std::shared_ptr display_pass; +}; + // Cost for the sectors in the grid // taken from Figure 23.1 in "Crowd Pathfinding and Steering Using Flow Field Tiles" const std::vector> sectors_cost = { @@ -194,4 +334,6 @@ const std::vector> sectors_cost = { // clang-format on }}; -} // namespace openage::path::tests +} // namespace tests +} // namespace path +} // namespace openage From 9d9000496c49ffef04e29f4dcad8cbd21c842684 Mon Sep 17 00:00:00 2001 From: heinezen Date: Wed, 27 Mar 2024 23:12:43 +0100 Subject: [PATCH 067/112] path: Render grid in demo 1. --- libopenage/pathfinding/demo/demo_1.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/libopenage/pathfinding/demo/demo_1.cpp b/libopenage/pathfinding/demo/demo_1.cpp index c25bda4390..a9a3171a67 100644 --- a/libopenage/pathfinding/demo/demo_1.cpp +++ b/libopenage/pathfinding/demo/demo_1.cpp @@ -99,6 +99,7 @@ void RenderManager1::run() { this->renderer->render(this->background_pass); this->renderer->render(this->field_pass); + this->renderer->render(this->grid_pass); this->renderer->render(this->display_pass); this->window->update(); @@ -169,9 +170,10 @@ void RenderManager1::init_passes() { // Create object for the grid auto model = Eigen::Affine3f::Identity(); model.prescale(Eigen::Vector3f{ - 1.0f / (this->grid->get_size()[0] * this->grid->get_sector_size()), - 1.0f / (this->grid->get_size()[1] * this->grid->get_sector_size()), + 2.0f / (this->grid->get_size()[0] * this->grid->get_sector_size()), + 2.0f / (this->grid->get_size()[1] * this->grid->get_sector_size()), 1.0f}); + model.pretranslate(Eigen::Vector3f{-1.0f, -1.0f, 0.0f}); auto grid_unifs = grid_shader->new_uniform_input( "model", model.matrix(), From ca2de6979d592385f1a9a02ba17ad67ba0a192b3 Mon Sep 17 00:00:00 2001 From: heinezen Date: Wed, 27 Mar 2024 23:35:19 +0100 Subject: [PATCH 068/112] path: Daw portal tiles on grid. --- libopenage/pathfinding/demo/demo_1.cpp | 78 ++++++++++++++++++++++++++ libopenage/pathfinding/demo/demo_1.h | 11 +++- 2 files changed, 86 insertions(+), 3 deletions(-) diff --git a/libopenage/pathfinding/demo/demo_1.cpp b/libopenage/pathfinding/demo/demo_1.cpp index a9a3171a67..c5a2e54755 100644 --- a/libopenage/pathfinding/demo/demo_1.cpp +++ b/libopenage/pathfinding/demo/demo_1.cpp @@ -155,6 +155,7 @@ void RenderManager1::init_passes() { auto field_fbo = renderer->create_texture_target({field_texture, depth_texture_2}); this->field_pass = renderer->add_render_pass({}, field_fbo); this->create_impassible_tiles(this->grid); + this->create_portal_tiles(this->grid); // Make a framebuffer for the grid render passes to draw into auto grid_texture = renderer->add_texture( @@ -387,4 +388,81 @@ void RenderManager1::create_impassible_tiles(const std::shared_ptr & } } +void RenderManager1::create_portal_tiles(const std::shared_ptr &grid) { + auto width = grid->get_size()[0]; + auto height = grid->get_size()[1]; + auto sector_size = grid->get_sector_size(); + + float tile_offset_width = 2.0f / (width * sector_size); + float tile_offset_height = 2.0f / (height * sector_size); + + for (size_t sector_x = 0; sector_x < width; sector_x++) { + for (size_t sector_y = 0; sector_y < height; sector_y++) { + auto sector = grid->get_sector(sector_x, sector_y); + + for (auto &portal : sector->get_portals()) { + auto start = portal->get_entry_start(sector->get_id()); + auto end = portal->get_entry_end(sector->get_id()); + auto direction = portal->get_direction(); + + std::vector tiles; + if (direction == PortalDirection::NORTH_SOUTH) { + auto y = start.se; + for (size_t x = start.ne; x <= end.ne; ++x) { + tiles.push_back(coord::tile{x, y}); + } + } + else { + auto x = start.ne; + for (size_t y = start.se; y <= end.se; ++y) { + tiles.push_back(coord::tile{x, y}); + } + } + + for (auto tile : tiles) { + std::array tile_data{ + -1.0f + tile.ne * tile_offset_width + sector_size * sector_x * tile_offset_width, + 1.0f - tile.se * tile_offset_height - sector_size * sector_y * tile_offset_height, + -1.0f + tile.ne * tile_offset_width + sector_size * sector_x * tile_offset_width, + 1.0f - (tile.se + 1) * tile_offset_height - sector_size * sector_y * tile_offset_height, + -1.0f + (tile.ne + 1) * tile_offset_width + sector_size * sector_x * tile_offset_width, + 1.0f - tile.se * tile_offset_height - sector_size * sector_y * tile_offset_height, + -1.0f + (tile.ne + 1) * tile_offset_width + sector_size * sector_x * tile_offset_width, + 1.0f - (tile.se + 1) * tile_offset_height - sector_size * sector_y * tile_offset_height, + }; + renderer::resources::VertexInputInfo info{ + {renderer::resources::vertex_input_t::V2F32}, + renderer::resources::vertex_layout_t::AOS, + renderer::resources::vertex_primitive_t::TRIANGLE_STRIP}; + + auto const data_size = tile_data.size() * sizeof(float); + std::vector verts(data_size); + std::memcpy(verts.data(), tile_data.data(), data_size); + + auto tile_mesh = renderer::resources::MeshData(std::move(verts), info); + auto tile_geometry = renderer->add_mesh_geometry(tile_mesh); + + Eigen::Matrix4f id_matrix = Eigen::Matrix4f::Identity(); + auto tile_unifs = obj_shader->new_uniform_input( + "color", + Eigen::Vector4f{0.0, 0.0, 0.0, 0.3}, + "model", + id_matrix, + "view", + id_matrix, + "proj", + id_matrix); + auto tile_obj = renderer::Renderable{ + tile_unifs, + tile_geometry, + true, + true, + }; + this->field_pass->add_renderables({tile_obj}); + } + } + } + } +} + } // namespace openage::path::tests diff --git a/libopenage/pathfinding/demo/demo_1.h b/libopenage/pathfinding/demo/demo_1.h index b067ff7553..52ed7d1f65 100644 --- a/libopenage/pathfinding/demo/demo_1.h +++ b/libopenage/pathfinding/demo/demo_1.h @@ -87,14 +87,19 @@ class RenderManager1 { static renderer::resources::MeshData get_grid_mesh(const std::shared_ptr &grid); /** - * Create objects for the impassible tiles in the grid. + * Create renderables for the impassible tiles in the grid. * * @param grid Pathing grid. - * - * @return Mesh data for the grid. */ void create_impassible_tiles(const std::shared_ptr &grid); + /** + * Create renderables for the portal tiles in the grid. + * + * @param grid Pathing grid. + */ + void create_portal_tiles(const std::shared_ptr &grid); + /** * Path to the project rootdir. */ From ea9f14306e1713b8407daa2cb55cef101e7d9c13 Mon Sep 17 00:00:00 2001 From: heinezen Date: Fri, 29 Mar 2024 14:47:27 +0100 Subject: [PATCH 069/112] path: Remove last tile check in portal search. --- libopenage/pathfinding/sector.cpp | 81 +++++++++++++------------------ 1 file changed, 34 insertions(+), 47 deletions(-) diff --git a/libopenage/pathfinding/sector.cpp b/libopenage/pathfinding/sector.cpp index 14c0ddeb36..c095c7b995 100644 --- a/libopenage/pathfinding/sector.cpp +++ b/libopenage/pathfinding/sector.cpp @@ -52,6 +52,7 @@ std::vector> Sector::find_portals(const std::shared_ptr< // compare the edges of the sectors size_t start; + size_t end; bool passable_edge; for (size_t i = 0; i < this->cost_field->get_size(); ++i) { auto coord_this = coord::tile{0, 0}; @@ -69,62 +70,48 @@ std::vector> Sector::find_portals(const std::shared_ptr< if (this->cost_field->get_cost(coord_this) != COST_IMPASSABLE and other_cost->get_cost(coord_other) != COST_IMPASSABLE) { + // both sides of the edge are passable if (not passable_edge) { + // start a new portal start = i; passable_edge = true; } - } - else { - if (passable_edge) { - auto coord_start = coord::tile{0, 0}; - auto coord_end = coord::tile{0, 0}; - if (direction == PortalDirection::NORTH_SOUTH) { - // right edge; top to bottom - coord_start = coord::tile{start, this->cost_field->get_size() - 1}; - coord_end = coord::tile{i - 1, this->cost_field->get_size() - 1}; - } - else if (direction == PortalDirection::EAST_WEST) { - // bottom edge; east to west - coord_start = coord::tile{this->cost_field->get_size() - 1, start}; - coord_end = coord::tile{this->cost_field->get_size() - 1, i - 1}; - } + // else: we already started a portal - result.push_back( - std::make_shared( - next_id, - this->id, - other->get_id(), - direction, - coord_start, - coord_end)); - passable_edge = false; - next_id += 1; + end = i; + if (i != this->cost_field->get_size() - 1) { + // continue to next tile unless we are at the last tile + // then we have to end the current portal + continue; } } - } - // recheck for the last tile on the edge - // because it may be the end of a portal - if (passable_edge) { - auto coord_start = coord::tile{0, 0}; - auto coord_end = coord::tile{0, 0}; - if (direction == PortalDirection::NORTH_SOUTH) { - coord_start = coord::tile{start, this->cost_field->get_size() - 1}; - coord_end = coord::tile{this->cost_field->get_size() - 1, this->cost_field->get_size() - 1}; - } - else if (direction == PortalDirection::EAST_WEST) { - coord_start = coord::tile{this->cost_field->get_size() - 1, start}; - coord_end = coord::tile{this->cost_field->get_size() - 1, this->cost_field->get_size() - 1}; - } + if (passable_edge) { + // create a new portal + auto coord_start = coord::tile{0, 0}; + auto coord_end = coord::tile{0, 0}; + if (direction == PortalDirection::NORTH_SOUTH) { + // right edge; top to bottom + coord_start = coord::tile{start, this->cost_field->get_size() - 1}; + coord_end = coord::tile{end, this->cost_field->get_size() - 1}; + } + else if (direction == PortalDirection::EAST_WEST) { + // bottom edge; east to west + coord_start = coord::tile{this->cost_field->get_size() - 1, start}; + coord_end = coord::tile{this->cost_field->get_size() - 1, end}; + } - result.push_back( - std::make_shared( - next_id, - this->id, - other->get_id(), - direction, - coord_start, - coord_end)); + result.push_back( + std::make_shared( + next_id, + this->id, + other->get_id(), + direction, + coord_start, + coord_end)); + passable_edge = false; + next_id += 1; + } } return result; From 3993d414a71af3095e2561ee784ab91857ad9e63 Mon Sep 17 00:00:00 2001 From: heinezen Date: Fri, 29 Mar 2024 14:49:51 +0100 Subject: [PATCH 070/112] path: Use matching types in flood fill algorithm. --- libopenage/pathfinding/sector.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libopenage/pathfinding/sector.cpp b/libopenage/pathfinding/sector.cpp index c095c7b995..09f35e16f8 100644 --- a/libopenage/pathfinding/sector.cpp +++ b/libopenage/pathfinding/sector.cpp @@ -144,13 +144,13 @@ void Sector::connect_exits() { if (portal->get_direction() == PortalDirection::NORTH_SOUTH) { // right edge; top to bottom - for (size_t i = start.se; i <= end.se; ++i) { + for (auto i = start.se; i <= end.se; ++i) { open_list.push_back(start.ne + i * this->cost_field->get_size()); } } else if (portal->get_direction() == PortalDirection::EAST_WEST) { // bottom edge; east to west - for (size_t i = start.ne; i <= end.ne; ++i) { + for (auto i = start.ne; i <= end.ne; ++i) { open_list.push_back(i + start.se * this->cost_field->get_size()); } } From d0f88dc0ad20fa3bcc1280550df74e174f31ec0d Mon Sep 17 00:00:00 2001 From: heinezen Date: Sat, 30 Mar 2024 21:20:27 +0100 Subject: [PATCH 071/112] path: Store sector position relative to grid origin --- libopenage/pathfinding/grid.cpp | 7 ++++++- libopenage/pathfinding/portal.cpp | 28 ++++++++++++++++++++++++++++ libopenage/pathfinding/portal.h | 22 ++++++++++++++++++++-- libopenage/pathfinding/sector.cpp | 10 ++++++++-- libopenage/pathfinding/sector.h | 17 +++++++++++++++++ 5 files changed, 79 insertions(+), 5 deletions(-) diff --git a/libopenage/pathfinding/grid.cpp b/libopenage/pathfinding/grid.cpp index 182bd71fc6..51940d2bf3 100644 --- a/libopenage/pathfinding/grid.cpp +++ b/libopenage/pathfinding/grid.cpp @@ -4,6 +4,7 @@ #include "error/error.h" +#include "coord/chunk.h" #include "pathfinding/cost_field.h" #include "pathfinding/sector.h" @@ -18,7 +19,11 @@ Grid::Grid(grid_id_t id, sector_size{sector_size} { for (size_t y = 0; y < size[1]; y++) { for (size_t x = 0; x < size[0]; x++) { - this->sectors.push_back(std::make_shared(x + y * this->size[0], sector_size)); + this->sectors.push_back( + std::make_shared( + x + y * this->size[0], + coord::chunk{x, y}, + sector_size)); } } } diff --git a/libopenage/pathfinding/portal.cpp b/libopenage/pathfinding/portal.cpp index 142780843b..da3581047f 100644 --- a/libopenage/pathfinding/portal.cpp +++ b/libopenage/pathfinding/portal.cpp @@ -75,6 +75,20 @@ const coord::tile Portal::get_entry_start(sector_id_t entry_sector) const { return this->get_sector1_start(); } +const coord::tile Portal::get_entry_center(sector_id_t entry_sector) const { + ENSURE(entry_sector == this->sector0 || entry_sector == this->sector1, "Invalid entry sector"); + + if (entry_sector == this->sector0) { + auto start = this->get_sector0_start(); + auto end = this->get_sector0_end(); + return {(start.ne + end.ne) / 2, (start.se + end.se) / 2}; + } + + auto start = this->get_sector1_start(); + auto end = this->get_sector1_end(); + return {(start.ne + end.ne) / 2, (start.se + end.se) / 2}; +} + const coord::tile Portal::get_entry_end(sector_id_t entry_sector) const { ENSURE(entry_sector == this->sector0 || entry_sector == this->sector1, "Invalid entry sector"); @@ -95,6 +109,20 @@ const coord::tile Portal::get_exit_start(sector_id_t entry_sector) const { return this->get_sector0_start(); } +const coord::tile Portal::get_exit_center(sector_id_t entry_sector) const { + ENSURE(entry_sector == this->sector0 || entry_sector == this->sector1, "Invalid entry sector"); + + if (entry_sector == this->sector0) { + auto start = this->get_sector1_start(); + auto end = this->get_sector1_end(); + return {(start.ne + end.ne) / 2, (start.se + end.se) / 2}; + } + + auto start = this->get_sector0_start(); + auto end = this->get_sector0_end(); + return {(start.ne + end.ne) / 2, (start.se + end.se) / 2}; +} + const coord::tile Portal::get_exit_end(sector_id_t entry_sector) const { ENSURE(entry_sector == this->sector0 || entry_sector == this->sector1, "Invalid entry sector"); diff --git a/libopenage/pathfinding/portal.h b/libopenage/pathfinding/portal.h index 509ef0b318..60a6bf9e48 100644 --- a/libopenage/pathfinding/portal.h +++ b/libopenage/pathfinding/portal.h @@ -106,14 +106,23 @@ class Portal { 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. + * Get the cell coordinates of the start of the portal in the entry sector. * * @param entry_sector Sector from which the portal is entered. * - * @return Cost field of the sector from which the portal is entered. + * @return Cell coordinates of the start of the portal in the entry sector. */ const coord::tile get_entry_start(sector_id_t entry_sector) const; + /** + * Get the cell coordinates of the center of the portal in the entry sector. + * + * @param entry_sector Sector from which the portal is entered. + * + * @return Cell coordinates of the center of the portal in the entry sector. + */ + const coord::tile get_entry_center(sector_id_t entry_sector) const; + /** * Get the cell coordinates of the start of the portal in the entry sector. * @@ -132,6 +141,15 @@ class Portal { */ const coord::tile get_exit_start(sector_id_t entry_sector) const; + /** + * Get the cell coordinates of the center of the portal in the exit sector. + * + * @param entry_sector Sector from which the portal is entered. + * + * @return Cell coordinates of the center of the portal in the exit sector. + */ + const coord::tile get_exit_center(sector_id_t entry_sector) const; + /** * Get the cell coordinates of the end of the portal in the exit sector. * diff --git a/libopenage/pathfinding/sector.cpp b/libopenage/pathfinding/sector.cpp index 09f35e16f8..bc75894cb8 100644 --- a/libopenage/pathfinding/sector.cpp +++ b/libopenage/pathfinding/sector.cpp @@ -14,13 +14,15 @@ namespace openage::path { -Sector::Sector(sector_id_t id, size_t field_size) : +Sector::Sector(sector_id_t id, const coord::chunk &position, size_t field_size) : id{id}, + position{position}, cost_field{std::make_shared(field_size)} { } -Sector::Sector(sector_id_t id, const std::shared_ptr &cost_field) : +Sector::Sector(sector_id_t id, const coord::chunk &position, const std::shared_ptr &cost_field) : id{id}, + position{position}, cost_field{cost_field} { } @@ -28,6 +30,10 @@ const sector_id_t &Sector::get_id() const { return this->id; } +const coord::chunk &Sector::get_position() const { + return this->position; +} + const std::shared_ptr &Sector::get_cost_field() const { return this->cost_field; } diff --git a/libopenage/pathfinding/sector.h b/libopenage/pathfinding/sector.h index 84f4bea0c4..f39f1904a9 100644 --- a/libopenage/pathfinding/sector.h +++ b/libopenage/pathfinding/sector.h @@ -6,6 +6,7 @@ #include #include +#include "coord/chunk.h" #include "pathfinding/portal.h" #include "pathfinding/types.h" @@ -26,18 +27,22 @@ class Sector { * Create a new sector with a specified ID and an uninitialized cost field. * * @param id ID of the sector. Should be unique per grid. + * @param position Position of the sector in the grid. * @param field_size Size of the cost field. */ Sector(sector_id_t id, + const coord::chunk &position, size_t field_size); /** * Create a new sector with a specified ID and an existing cost field. * * @param id ID of the sector. Should be unique per grid. + * @param position Position of the sector in the grid. * @param cost_field Cost field of the sector. */ Sector(sector_id_t id, + const coord::chunk &position, const std::shared_ptr &cost_field); /** @@ -49,6 +54,13 @@ class Sector { */ const sector_id_t &get_id() const; + /** + * Get the position of this sector in the grid. + * + * @return Position of the sector. + */ + const coord::chunk &get_position() const; + /** * Get the cost field of this sector. * @@ -97,6 +109,11 @@ class Sector { */ sector_id_t id; + /** + * Position of the sector in the grid. + */ + coord::chunk position; + /** * Cost field of the sector. */ From bc9279a22b6933617c70b237f5680ba3876fdede Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 31 Mar 2024 00:37:59 +0100 Subject: [PATCH 072/112] path: Port A* algorithm code to grid portals. --- libopenage/pathfinding/pathfinder.cpp | 191 ++++++++++++++++++++++++++ libopenage/pathfinding/pathfinder.h | 123 ++++++++++++++++- 2 files changed, 312 insertions(+), 2 deletions(-) diff --git a/libopenage/pathfinding/pathfinder.cpp b/libopenage/pathfinding/pathfinder.cpp index 44be92ccd1..c158c57bee 100644 --- a/libopenage/pathfinding/pathfinder.cpp +++ b/libopenage/pathfinding/pathfinder.cpp @@ -2,7 +2,11 @@ #include "pathfinder.h" +#include "coord/phys.h" +#include "pathfinding/grid.h" #include "pathfinding/integrator.h" +#include "pathfinding/portal.h" +#include "pathfinding/sector.h" namespace openage::path { @@ -12,8 +16,195 @@ Pathfinder::Pathfinder() : integrator{std::make_shared()} { } +const Path Pathfinder::get_path(PathRequest &request) { + auto portal_path = this->portal_a_star(request); + + // TODO: Implement the rest of the pathfinding process +} + const std::shared_ptr &Pathfinder::get_grid(size_t id) const { return this->grids.at(id); } +const std::vector> Pathfinder::portal_a_star(PathRequest &request) const { + std::vector> result; + + auto grid = this->grids.at(request.grid_id); + auto sector_size = grid->get_sector_size(); + + auto start_sector_x = request.start.ne / sector_size; + auto start_sector_y = request.start.se / sector_size; + auto start_sector = grid->get_sector(start_sector_x, start_sector_y); + + auto target_sector_x = request.target.ne / sector_size; + auto target_sector_y = request.target.se / sector_size; + auto target_sector = grid->get_sector(target_sector_x, target_sector_y); + + // path node storage, always provides cheapest next node. + PortalNode::heap_t node_candidates; + + // list of known portals and corresponding node. + PortalNode::nodemap_t visited_portals; + + // Cost to travel from one portal to another + // TODO: Determine this cost for each portal + const int distance_cost = 1; + + // start nodes: all portals in the start sector + for (auto &portal : start_sector->get_portals()) { + auto portal_node = std::make_shared(portal, start_sector->get_id(), nullptr); + + auto sector_pos = grid->get_sector(portal->get_exit_sector(start_sector->get_id()))->get_position(); + auto portal_pos = portal->get_exit_center(start_sector->get_id()); + auto portal_abs_pos = portal_pos + coord::tile_delta{sector_pos.ne * sector_size, sector_pos.se * sector_size}; + auto heuristic_cost = Pathfinder::heuristic_cost(portal_abs_pos, request.target); + + portal_node->current_cost = 0; + portal_node->heuristic_cost = heuristic_cost; + 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; + } + + // track the closest we can get to the end position + // used when no path is found + auto closest_node = node_candidates.top(); + + // while there are candidates to visit + while (not node_candidates.empty()) { + auto current_node = node_candidates.pop(); + + current_node->was_best = true; + + // check if the current node is the target + if (current_node->portal->get_exit_sector(current_node->entry_sector) == target_sector->get_id()) { + auto backtrace = current_node->generate_backtrace(); + for (auto &node : backtrace) { + result.push_back(node->portal); + } + return result; + } + + // check if the current node is the closest to the target + if (current_node->heuristic_cost < closest_node->heuristic_cost) { + closest_node = current_node; + } + + // get the exits of the current node + auto exits = current_node->get_exits(visited_portals, current_node->entry_sector); + + // evaluate all neighbors of the current candidate for further progress + for (auto &exit : exits) { + if (exit->was_best) { + continue; + } + + 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) { + if (not_visited) { + // calculate the heuristic cost + exit->heuristic_cost = Pathfinder::heuristic_cost(exit->portal->get_exit_center(exit->entry_sector), request.target); + } + + // update the cost knowledge + exit->current_cost = tentative_cost; + exit->future_cost = exit->current_cost + exit->heuristic_cost; + exit->prev_portal = current_node; + + if (not_visited) { + exit->heap_node = node_candidates.push(exit); + visited_portals[exit->portal->get_id()] = exit; + } + else { + node_candidates.decrease(exit->heap_node); + } + } + } + } + + // no path found, return the closest node + auto backtrace = closest_node->generate_backtrace(); + for (auto &node : backtrace) { + result.push_back(node->portal); + } + + return result; +} + +int Pathfinder::heuristic_cost(const coord::tile &portal_pos, + const coord::tile &target_pos) { + auto portal_phys_pos = portal_pos.to_phys2(); + auto target_phys_pos = target_pos.to_phys2(); + auto delta = target_phys_pos - portal_phys_pos; + + return delta.length(); +} + +PortalNode::PortalNode(const std::shared_ptr &portal, + sector_id_t entry_sector, + const node_t &prev_portal) : + portal{portal}, + entry_sector{entry_sector}, + was_best{false}, + prev_portal{prev_portal}, + heap_node{nullptr} {} + +PortalNode::PortalNode(const std::shared_ptr &portal, + sector_id_t entry_sector, + const node_t &prev_portal, + int past_cost, + int heuristic_cost) : + portal{portal}, + entry_sector{entry_sector}, + future_cost{past_cost + heuristic_cost}, + current_cost{past_cost}, + heuristic_cost{heuristic_cost}, + was_best{false}, + prev_portal{prev_portal}, + heap_node{nullptr} { +} + +bool PortalNode::operator<(const PortalNode &other) const { + return this->future_cost < other.future_cost; +} + +bool PortalNode::operator==(const PortalNode &other) const { + return this->portal->get_id() == other.portal->get_id(); +} + +std::vector PortalNode::generate_backtrace() { + std::vector waypoints; + + node_t current = this->shared_from_this(); + do { + node_t other = current; + waypoints.push_back(current); + current = current->prev_portal; + } + while (current != nullptr); + waypoints.pop_back(); // remove start + + return waypoints; +} + +std::vector PortalNode::get_exits(const nodemap_t &nodes, + sector_id_t entry_sector) { + std::vector exits; + for (auto &exit : this->portal->get_exits(entry_sector)) { + auto exit_id = exit->get_id(); + + if (nodes.contains(exit_id)) { + exits.push_back(nodes.at(exit_id)); + } + else { + exits.push_back(std::make_shared(exit, entry_sector, this->shared_from_this())); + } + } + return exits; +} + + } // namespace openage::path diff --git a/libopenage/pathfinding/pathfinder.h b/libopenage/pathfinding/pathfinder.h index d423272ff1..e1e04a4a17 100644 --- a/libopenage/pathfinding/pathfinder.h +++ b/libopenage/pathfinding/pathfinder.h @@ -5,10 +5,16 @@ #include #include +#include "coord/tile.h" +#include "datastructure/pairing_heap.h" +#include "pathfinding/path.h" +#include "pathfinding/types.h" + namespace openage::path { class Grid; class Integrator; +class Portal; /** * Pathfinder for flow field pathfinding. @@ -36,15 +42,36 @@ class Pathfinder { * * @return Pathfinding grid. */ - const std::shared_ptr &get_grid(size_t id) const; + const std::shared_ptr &get_grid(grid_id_t id) const; + + /** + * Get the path for a pathfinding request. + * + * @param request Pathfinding request. + * + * @return Path found by the pathfinder. + */ + const Path get_path(PathRequest &request); private: + const std::vector> portal_a_star(PathRequest &request) const; + + /** + * Calculate the heuristic cost between a portal and a target cell. + * + * @param portal_pos Position of the portal. This should be the center of the portal exit. + * @param target_pos Position of the target cell. + * + * @return Heuristic cost between the cells. + */ + static int heuristic_cost(const coord::tile &portal_pos, const coord::tile &target_pos); + /** * Grids managed by this pathfinder. * * Each grid can have separate pathing. */ - std::unordered_map> grids; + std::unordered_map> grids; /** * Integrator for flow field calculations. @@ -52,4 +79,96 @@ class Pathfinder { std::shared_ptr integrator; }; + +/** + * One navigation waypoint in a path. + */ +class PortalNode : public std::enable_shared_from_this { +public: + using node_t = std::shared_ptr; + using heap_t = datastructure::PairingHeap; + using nodemap_t = std::unordered_map; + + PortalNode(const std::shared_ptr &portal, + sector_id_t entry_sector, + const node_t &prev_portal); + PortalNode(const std::shared_ptr &portal, + sector_id_t entry_sector, + const node_t &prev_portal, + int past_cost, + int heuristic_cost); + + /** + * Orders nodes according to their future cost value. + */ + bool operator<(const PortalNode &other) const; + + /** + * Compare the node to another one. + * They are the same if their portal is. + */ + bool operator==(const PortalNode &other) const; + + /** + * Calculates the actual movement cose to another node. + */ + int cost_to(const PortalNode &other) const; + + /** + * Create a backtrace path beginning at this node. + */ + std::vector generate_backtrace(); + + /** + * Get all exits of a node. + */ + std::vector get_exits(const nodemap_t &nodes, sector_id_t entry_sector); + + /** + * The portal this node is associated to. + */ + std::shared_ptr portal; + + /** + * Sector where the portal is entered. + */ + sector_id_t entry_sector; + + /** + * Future cost estimation value for this node. + */ + int future_cost; + + /** + * Evaluated past cost value for the node. + * This stores the actual cost from start to this node. + */ + int current_cost; + + /** + * Heuristic cost cache. + * Calculated once, is the heuristic distance from this node + * to the goal. + */ + int heuristic_cost; + + /** + * Does this node already have an alternative path? + * If the node was once selected as the best next hop, + * this is set to true. + */ + bool was_best = false; + + /** + * Node where this one was reached by least cost. + */ + node_t prev_portal; + + /** + * Priority queue node that contains this path node. + */ + heap_t::element_t heap_node; +}; + + } // namespace openage::path From 42393b84c514a0765d61faaa179f629677f62ca2 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 31 Mar 2024 17:03:27 +0200 Subject: [PATCH 073/112] path: Fix node cost comparison on heap. --- libopenage/pathfinding/pathfinder.cpp | 35 ++++++++++++++++++++------- libopenage/pathfinding/pathfinder.h | 29 +++++++++++++++++++--- 2 files changed, 51 insertions(+), 13 deletions(-) diff --git a/libopenage/pathfinding/pathfinder.cpp b/libopenage/pathfinding/pathfinder.cpp index c158c57bee..5d1e255d84 100644 --- a/libopenage/pathfinding/pathfinder.cpp +++ b/libopenage/pathfinding/pathfinder.cpp @@ -20,12 +20,17 @@ const Path Pathfinder::get_path(PathRequest &request) { auto portal_path = this->portal_a_star(request); // TODO: Implement the rest of the pathfinding process + return Path{}; } -const std::shared_ptr &Pathfinder::get_grid(size_t id) const { +const std::shared_ptr &Pathfinder::get_grid(grid_id_t id) const { return this->grids.at(id); } +void Pathfinder::add_grid(const std::shared_ptr &grid) { + this->grids[grid->get_id()] = grid; +} + const std::vector> Pathfinder::portal_a_star(PathRequest &request) const { std::vector> result; @@ -41,10 +46,10 @@ const std::vector> Pathfinder::portal_a_star(PathRequest auto target_sector = grid->get_sector(target_sector_x, target_sector_y); // path node storage, always provides cheapest next node. - PortalNode::heap_t node_candidates; + heap_t node_candidates; // list of known portals and corresponding node. - PortalNode::nodemap_t visited_portals; + nodemap_t visited_portals; // Cost to travel from one portal to another // TODO: Determine this cost for each portal @@ -106,7 +111,9 @@ const std::vector> Pathfinder::portal_a_star(PathRequest if (not_visited or tentative_cost < exit->current_cost) { if (not_visited) { // calculate the heuristic cost - exit->heuristic_cost = Pathfinder::heuristic_cost(exit->portal->get_exit_center(exit->entry_sector), request.target); + exit->heuristic_cost = Pathfinder::heuristic_cost( + exit->portal->get_exit_center(exit->entry_sector), + request.target); } // update the cost knowledge @@ -148,6 +155,9 @@ PortalNode::PortalNode(const std::shared_ptr &portal, const node_t &prev_portal) : portal{portal}, entry_sector{entry_sector}, + future_cost{std::numeric_limits::max()}, + current_cost{std::numeric_limits::max()}, + heuristic_cost{std::numeric_limits::max()}, was_best{false}, prev_portal{prev_portal}, heap_node{nullptr} {} @@ -175,7 +185,7 @@ bool PortalNode::operator==(const PortalNode &other) const { return this->portal->get_id() == other.portal->get_id(); } -std::vector PortalNode::generate_backtrace() { +std::vector PortalNode::generate_backtrace() { std::vector waypoints; node_t current = this->shared_from_this(); @@ -185,14 +195,15 @@ std::vector PortalNode::generate_backtrace() { current = current->prev_portal; } while (current != nullptr); - waypoints.pop_back(); // remove start return waypoints; } -std::vector PortalNode::get_exits(const nodemap_t &nodes, - sector_id_t entry_sector) { +std::vector PortalNode::get_exits(const nodemap_t &nodes, + sector_id_t entry_sector) { std::vector exits; + + auto exit_sector = this->portal->get_exit_sector(entry_sector); for (auto &exit : this->portal->get_exits(entry_sector)) { auto exit_id = exit->get_id(); @@ -200,11 +211,17 @@ std::vector PortalNode::get_exits(const nodemap_t &nodes, exits.push_back(nodes.at(exit_id)); } else { - exits.push_back(std::make_shared(exit, entry_sector, this->shared_from_this())); + exits.push_back(std::make_shared(exit, + exit_sector, + this->shared_from_this())); } } return exits; } +bool compare_node_cost::operator()(const node_t &lhs, const node_t &rhs) const { + return *lhs < *rhs; +} + } // namespace openage::path diff --git a/libopenage/pathfinding/pathfinder.h b/libopenage/pathfinding/pathfinder.h index e1e04a4a17..b89f994bc8 100644 --- a/libopenage/pathfinding/pathfinder.h +++ b/libopenage/pathfinding/pathfinder.h @@ -44,6 +44,13 @@ class Pathfinder { */ const std::shared_ptr &get_grid(grid_id_t id) const; + /** + * Add a grid to the pathfinder. + * + * @param grid Grid to add. + */ + void add_grid(const std::shared_ptr &grid); + /** * Get the path for a pathfinding request. * @@ -80,15 +87,29 @@ class Pathfinder { }; +class PortalNode; + +using node_t = std::shared_ptr; + +/** + * Cost comparison for node_t on the pairing heap. + * + * Extracts the nodes from the shared_ptr and compares them. We have + * to use a custom comparison function because otherwise the shared_ptr + * would be compared instead of the actual node. + */ +struct compare_node_cost { + bool operator()(const node_t &lhs, const node_t &rhs) const; +}; + +using heap_t = datastructure::PairingHeap; +using nodemap_t = std::unordered_map; + /** * One navigation waypoint in a path. */ class PortalNode : public std::enable_shared_from_this { public: - using node_t = std::shared_ptr; - using heap_t = datastructure::PairingHeap; - using nodemap_t = std::unordered_map; - PortalNode(const std::shared_ptr &portal, sector_id_t entry_sector, const node_t &prev_portal); From 16710e912c38e5b2b6fa128de770338e463a38df Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 31 Mar 2024 17:03:43 +0200 Subject: [PATCH 074/112] path: Calculate example path in demo. --- libopenage/pathfinding/demo/demo_1.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/libopenage/pathfinding/demo/demo_1.cpp b/libopenage/pathfinding/demo/demo_1.cpp index c5a2e54755..c2f5ba19de 100644 --- a/libopenage/pathfinding/demo/demo_1.cpp +++ b/libopenage/pathfinding/demo/demo_1.cpp @@ -4,6 +4,8 @@ #include "pathfinding/cost_field.h" #include "pathfinding/grid.h" +#include "pathfinding/path.h" +#include "pathfinding/pathfinder.h" #include "pathfinding/portal.h" #include "pathfinding/sector.h" @@ -70,6 +72,16 @@ void path_demo_1(const util::Path &path) { } } + auto pathfinder = std::make_shared(); + pathfinder->add_grid(grid); + + auto path_request = path::PathRequest{ + 0, + coord::tile{2, 26}, + coord::tile{36, 2}, + }; + auto path_result = pathfinder->get_path(path_request); + // Render the grid auto qtapp = std::make_shared(); auto window = std::make_shared("openage pathfinding test", 1024, 768); From de9dc91abbd3b60a2332374ddbb9481dc6e8edcd Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 31 Mar 2024 17:10:25 +0200 Subject: [PATCH 075/112] path: Remove unnecessary parameter. --- libopenage/pathfinding/pathfinder.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/libopenage/pathfinding/pathfinder.cpp b/libopenage/pathfinding/pathfinder.cpp index 5d1e255d84..22fa2b0534 100644 --- a/libopenage/pathfinding/pathfinder.cpp +++ b/libopenage/pathfinding/pathfinder.cpp @@ -190,7 +190,6 @@ std::vector PortalNode::generate_backtrace() { node_t current = this->shared_from_this(); do { - node_t other = current; waypoints.push_back(current); current = current->prev_portal; } From 46a544f427b54a3725d7874144fdbf8be2d721df Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 1 Apr 2024 23:48:48 +0200 Subject: [PATCH 076/112] path: Remove cost field storage from integrator. --- libopenage/pathfinding/integrator.cpp | 15 ++++++--------- libopenage/pathfinding/integrator.h | 16 ++++------------ libopenage/pathfinding/tests.cpp | 3 +-- 3 files changed, 11 insertions(+), 23 deletions(-) diff --git a/libopenage/pathfinding/integrator.cpp b/libopenage/pathfinding/integrator.cpp index 3e9ecbe902..9c5d6020e0 100644 --- a/libopenage/pathfinding/integrator.cpp +++ b/libopenage/pathfinding/integrator.cpp @@ -9,16 +9,13 @@ namespace openage::path { -void Integrator::set_cost_field(const std::shared_ptr &cost_field) { - this->cost_field = cost_field; -} - -std::shared_ptr Integrator::build(const coord::tile &target) { - auto flow_field = std::make_shared(this->cost_field->get_size()); - auto integrate_field = std::make_shared(this->cost_field->get_size()); +std::shared_ptr Integrator::build(const std::shared_ptr &cost_field, + const coord::tile &target) { + auto flow_field = std::make_shared(cost_field->get_size()); + auto integrate_field = std::make_shared(cost_field->get_size()); - auto wavefront_blocked = integrate_field->integrate_los(this->cost_field, target); - integrate_field->integrate_cost(this->cost_field, std::move(wavefront_blocked)); + auto wavefront_blocked = integrate_field->integrate_los(cost_field, target); + integrate_field->integrate_cost(cost_field, std::move(wavefront_blocked)); flow_field->build(integrate_field); return flow_field; diff --git a/libopenage/pathfinding/integrator.h b/libopenage/pathfinding/integrator.h index fc0d433c1d..2af2bf1e0c 100644 --- a/libopenage/pathfinding/integrator.h +++ b/libopenage/pathfinding/integrator.h @@ -24,26 +24,18 @@ class Integrator { ~Integrator() = default; /** - * Set the cost field. + * Build the flow field for a target. * * @param cost_field Cost field. - */ - void set_cost_field(const std::shared_ptr &cost_field); - - /** - * Build the flow field for a target cell. - * * @param target Coordinates of the target cell. * * @return Flow field. */ - std::shared_ptr build(const coord::tile &target); + std::shared_ptr build(const std::shared_ptr &cost_field, + const coord::tile &target); private: - /** - * Cost field. - */ - std::shared_ptr cost_field; + // TODO: Cache field results. }; } // namespace path diff --git a/libopenage/pathfinding/tests.cpp b/libopenage/pathfinding/tests.cpp index ab62d2ff2f..ea30a3d2a5 100644 --- a/libopenage/pathfinding/tests.cpp +++ b/libopenage/pathfinding/tests.cpp @@ -83,10 +83,9 @@ void flow_field() { { // Integrator for managing the flow field auto integrator = std::make_shared(); - integrator->set_cost_field(cost_field); // Build the flow field - auto flow_field = integrator->build(coord::tile{2, 2}); + auto flow_field = integrator->build(cost_field, coord::tile{2, 2}); auto ff_cells = flow_field->get_cells(); // The flow field for targeting (2, 2) hould look like this: From 481465974fe81a1dc5ff68584384058ed59d9e46 Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 1 Apr 2024 23:54:05 +0200 Subject: [PATCH 077/112] path: Use legacy namespace for old code. --- libopenage/pathfinding/legacy/a_star.cpp | 4 +- libopenage/pathfinding/legacy/a_star.h | 4 +- libopenage/pathfinding/legacy/heuristics.cpp | 5 +- libopenage/pathfinding/legacy/heuristics.h | 4 +- libopenage/pathfinding/legacy/path.cpp | 4 +- libopenage/pathfinding/legacy/path.h | 10 +-- libopenage/pathfinding/legacy/path_utils.h | 2 +- libopenage/pathfinding/legacy/tests.cpp | 64 ++++++++++---------- 8 files changed, 49 insertions(+), 48 deletions(-) diff --git a/libopenage/pathfinding/legacy/a_star.cpp b/libopenage/pathfinding/legacy/a_star.cpp index 640a20bb32..afeabeeefd 100644 --- a/libopenage/pathfinding/legacy/a_star.cpp +++ b/libopenage/pathfinding/legacy/a_star.cpp @@ -22,7 +22,7 @@ namespace openage { -namespace path { +namespace path::legacy { Path to_point(coord::phys3 start, @@ -128,5 +128,5 @@ Path a_star(coord::phys3 start, } -} // namespace path +} // namespace path::legacy } // namespace openage diff --git a/libopenage/pathfinding/legacy/a_star.h b/libopenage/pathfinding/legacy/a_star.h index 24e09fee2e..a2704bb5f1 100644 --- a/libopenage/pathfinding/legacy/a_star.h +++ b/libopenage/pathfinding/legacy/a_star.h @@ -9,7 +9,7 @@ namespace openage { -namespace path { +namespace path::legacy { /** * path between two static points @@ -38,5 +38,5 @@ Path a_star(coord::phys3 start, std::function heuristic, std::function passable); -} // namespace path +} // namespace path::legacy } // namespace openage diff --git a/libopenage/pathfinding/legacy/heuristics.cpp b/libopenage/pathfinding/legacy/heuristics.cpp index 51a24618a0..e064fa1ad7 100644 --- a/libopenage/pathfinding/legacy/heuristics.cpp +++ b/libopenage/pathfinding/legacy/heuristics.cpp @@ -7,7 +7,7 @@ namespace openage { -namespace path { +namespace path::legacy { cost_old_t manhattan_cost(const coord::phys3 &start, const coord::phys3 &end) { cost_old_t dx = std::abs(start.ne - end.ne).to_float(); @@ -31,4 +31,5 @@ cost_old_t euclidean_squared_cost(const coord::phys3 &start, const coord::phys3 return dx * dx + dy * dy; } -}} // openage::path +} // namespace path::legacy +} // namespace openage diff --git a/libopenage/pathfinding/legacy/heuristics.h b/libopenage/pathfinding/legacy/heuristics.h index e9e9a03709..77707fbc9f 100644 --- a/libopenage/pathfinding/legacy/heuristics.h +++ b/libopenage/pathfinding/legacy/heuristics.h @@ -5,7 +5,7 @@ #include "pathfinding/legacy/path.h" namespace openage { -namespace path { +namespace path::legacy { /** * function pointer type for distance estimation functions. @@ -41,5 +41,5 @@ cost_old_t euclidean_squared_cost(const coord::phys3 &start, const coord::phys3 */ cost_old_t euclidean_squared_to_euclidean_cost(const cost_old_t euclidean_squared_value); -} // namespace path +} // namespace path::legacy } // namespace openage diff --git a/libopenage/pathfinding/legacy/path.cpp b/libopenage/pathfinding/legacy/path.cpp index 6db3c528f9..dafb63e686 100644 --- a/libopenage/pathfinding/legacy/path.cpp +++ b/libopenage/pathfinding/legacy/path.cpp @@ -4,7 +4,7 @@ #include "pathfinding/legacy/path.h" -namespace openage::path { +namespace openage::path::legacy { bool compare_node_cost::operator()(const node_pt &lhs, const node_pt &rhs) const { @@ -112,4 +112,4 @@ Path::Path(const std::vector &nodes) : waypoints{nodes} {} -} // namespace openage::path +} // namespace openage::path::legacy diff --git a/libopenage/pathfinding/legacy/path.h b/libopenage/pathfinding/legacy/path.h index a6d7aa80ae..59851ecd51 100644 --- a/libopenage/pathfinding/legacy/path.h +++ b/libopenage/pathfinding/legacy/path.h @@ -16,7 +16,7 @@ namespace openage { -namespace path { +namespace path::legacy { class Node; class Path; @@ -185,7 +185,7 @@ class Path { std::vector waypoints; }; -} // namespace path +} // namespace path::legacy } // namespace openage @@ -196,10 +196,10 @@ namespace std { * Just uses their position. */ template <> -struct hash { - size_t operator()(const openage::path::Node &x) const { +struct hash { + size_t operator()(const openage::path::legacy::Node &x) const { openage::coord::phys3 node_pos = x.position; - size_t hash = openage::util::type_hash(); + size_t hash = openage::util::type_hash(); hash = openage::util::hash_combine(hash, std::hash{}(node_pos.ne)); hash = openage::util::hash_combine(hash, std::hash{}(node_pos.se)); return hash; diff --git a/libopenage/pathfinding/legacy/path_utils.h b/libopenage/pathfinding/legacy/path_utils.h index de1aa99da8..2c87923ad4 100644 --- a/libopenage/pathfinding/legacy/path_utils.h +++ b/libopenage/pathfinding/legacy/path_utils.h @@ -3,7 +3,7 @@ #pragma once namespace openage { -namespace path { +namespace path::legacy { } // namespace path } // namespace openage diff --git a/libopenage/pathfinding/legacy/tests.cpp b/libopenage/pathfinding/legacy/tests.cpp index f68e512660..b160eff91a 100644 --- a/libopenage/pathfinding/legacy/tests.cpp +++ b/libopenage/pathfinding/legacy/tests.cpp @@ -23,11 +23,11 @@ void node_0() { coord::phys3 p5{2, 2, 0}; coord::phys3 p6{2, -2, 0}; - node_pt n0 = std::make_unique(p0, nullptr); - node_pt n1 = std::make_unique(p1, n0); - node_pt n2 = std::make_unique(p2, n1); - node_pt n3 = std::make_unique(p3, n1); - node_pt n4 = std::make_unique(p0, n1); + legacy::node_pt n0 = std::make_unique(p0, nullptr); + legacy::node_pt n1 = std::make_unique(p1, n0); + legacy::node_pt n2 = std::make_unique(p2, n1); + legacy::node_pt n3 = std::make_unique(p3, n1); + legacy::node_pt n4 = std::make_unique(p0, n1); // Testing how the factor is effected from the change in // direction from one node to another @@ -58,10 +58,10 @@ void node_0() { // Testing that the distance from the previous node noes not // effect the factor, only change in direction - n1 = std::make_unique(p4, n0); - n2 = std::make_unique(p5, n1); - n3 = std::make_unique(p6, n1); - n4 = std::make_unique(p0, n1); + n1 = std::make_unique(p4, n0); + n2 = std::make_unique(p5, n1); + n3 = std::make_unique(p6, n1); + n4 = std::make_unique(p0, n1); TESTEQUALS(n1->direction.ne, 1); TESTEQUALS(n1->direction.se, 0); @@ -95,8 +95,8 @@ void node_cost_to_0() { coord::phys3 p0{0, 0, 0}; coord::phys3 p1{10, 0, 0}; - node_pt n0 = std::make_unique(p0, nullptr); - node_pt n1 = std::make_unique(p1, nullptr); + legacy::node_pt n0 = std::make_unique(p0, nullptr); + legacy::node_pt n1 = std::make_unique(p1, nullptr); TESTEQUALS(n0->cost_to(*n1), 10); TESTEQUALS(n1->cost_to(*n0), 10); @@ -104,7 +104,7 @@ void node_cost_to_0() { // Testing basic cost_to with se only coord::phys3 p2{0, 5, 0}; - node_pt n2 = std::make_unique(p2, nullptr); + legacy::node_pt n2 = std::make_unique(p2, nullptr); TESTEQUALS(n0->cost_to(*n2), 5); TESTEQUALS(n2->cost_to(*n0), 5); @@ -112,14 +112,14 @@ void node_cost_to_0() { // Testing cost_to with both se and ne: coord::phys3 p3{3, 4, 0}; // -> sqrt(3*3 + 4*4) == 5 - node_pt n3 = std::make_unique(p3, nullptr); + legacy::node_pt n3 = std::make_unique(p3, nullptr); TESTEQUALS(n0->cost_to(*n3), 5); TESTEQUALS(n3->cost_to(*n0), 5); // Test cost_to and check that `up` has no effect coord::phys3 p4{3, 4, 8}; - node_pt n4 = std::make_unique(p4, nullptr); + legacy::node_pt n4 = std::make_unique(p4, nullptr); TESTEQUALS(n0->cost_to(*n4), 5); TESTEQUALS(n4->cost_to(*n0), 5); @@ -135,20 +135,20 @@ void node_cost_to_1() { coord::phys3 p0{-0.125, 0, 0}; coord::phys3 p1{0.125, 0, 0}; - node_pt n0 = std::make_unique(p0, nullptr); - node_pt n1 = std::make_unique(p1, n0); + legacy::node_pt n0 = std::make_unique(p0, nullptr); + legacy::node_pt n1 = std::make_unique(p1, n0); // We expect twice the normal cost since n0 had not direction // thus we get a factor of 2 on n1 TESTEQUALS_FLOAT(n0->cost_to(*n1), 0.5, 0.001); TESTEQUALS_FLOAT(n1->cost_to(*n0), 0.5, 0.001); - nodemap_t visited_tiles; + legacy::nodemap_t visited_tiles; visited_tiles[n0->position] = n0; // Collect the costs to go to all the neighbors of n1 std::vector costs; - for (node_pt neighbor : n1->get_neighbors(visited_tiles, 1)) { + for (legacy::node_pt neighbor : n1->get_neighbors(visited_tiles, 1)) { costs.push_back(n1->cost_to(*neighbor)); } @@ -172,12 +172,12 @@ void node_generate_backtrace_0() { coord::phys3 p2{20, 0, 0}; coord::phys3 p3{30, 0, 0}; - node_pt n0 = std::make_unique(p0, nullptr); - node_pt n1 = std::make_unique(p1, n0); - node_pt n2 = std::make_unique(p2, n1); - node_pt n3 = std::make_unique(p3, n2); + legacy::node_pt n0 = std::make_unique(p0, nullptr); + legacy::node_pt n1 = std::make_unique(p1, n0); + legacy::node_pt n2 = std::make_unique(p2, n1); + legacy::node_pt n3 = std::make_unique(p3, n2); - Path path = n3->generate_backtrace(); + legacy::Path path = n3->generate_backtrace(); (path.waypoints[0] == *n3) or TESTFAIL; (path.waypoints[1] == *n2) or TESTFAIL; @@ -191,13 +191,13 @@ void node_generate_backtrace_0() { void node_get_neighbors_0() { coord::phys3 p0{0, 0, 0}; - node_pt n0 = std::make_unique(p0, nullptr); - nodemap_t map; + legacy::node_pt n0 = std::make_unique(p0, nullptr); + legacy::nodemap_t map; // Testing get_neighbors returning all surounding tiles with // a factor of 1 - std::vector neighbors = n0->get_neighbors(map, 1); + std::vector neighbors = n0->get_neighbors(map, 1); TESTEQUALS(neighbors.size(), 8); TESTEQUALS_FLOAT(neighbors[0]->position.ne.to_double(), 0.125, 0.001); @@ -288,16 +288,16 @@ void node_passable_line_0() { coord::phys3 p0{0, 0, 0}; coord::phys3 p1{1000, 0, 0}; - node_pt n0 = std::make_unique(p0, nullptr); - node_pt n1 = std::make_unique(p1, n0); + legacy::node_pt n0 = std::make_unique(p0, nullptr); + legacy::node_pt n1 = std::make_unique(p1, n0); - TESTEQUALS(path::passable_line(n0, n1, path::tests::always_passable), true); - TESTEQUALS(path::passable_line(n0, n1, path::tests::not_passable), false); + TESTEQUALS(path::legacy::passable_line(n0, n1, path::tests::always_passable), true); + TESTEQUALS(path::legacy::passable_line(n0, n1, path::tests::not_passable), false); // The next 2 cases show that a different sample can change the results // for the same path - TESTEQUALS(path::passable_line(n0, n1, path::tests::sometimes_passable, 10), true); - TESTEQUALS(path::passable_line(n0, n1, path::tests::sometimes_passable, 50), false); + TESTEQUALS(path::legacy::passable_line(n0, n1, path::tests::sometimes_passable, 10), true); + TESTEQUALS(path::legacy::passable_line(n0, n1, path::tests::sometimes_passable, 50), false); } /** From ba505e9755be6a6bff200227ec1f085a76c01558 Mon Sep 17 00:00:00 2001 From: heinezen Date: Tue, 2 Apr 2024 23:39:03 +0200 Subject: [PATCH 078/112] path: Integrate cost via portal. --- libopenage/pathfinding/integration_field.cpp | 46 ++++++++++++++++++++ libopenage/pathfinding/integration_field.h | 20 ++++++++- libopenage/pathfinding/pathfinder.cpp | 17 ++++++++ 3 files changed, 81 insertions(+), 2 deletions(-) diff --git a/libopenage/pathfinding/integration_field.cpp b/libopenage/pathfinding/integration_field.cpp index 9881ec0f3c..9ff6e0854a 100644 --- a/libopenage/pathfinding/integration_field.cpp +++ b/libopenage/pathfinding/integration_field.cpp @@ -10,6 +10,7 @@ #include "coord/tile.h" #include "pathfinding/cost_field.h" #include "pathfinding/definitions.h" +#include "pathfinding/portal.h" namespace openage::path { @@ -163,6 +164,51 @@ void IntegrationField::integrate_cost(const std::shared_ptr &cost_fie this->integrate_cost(cost_field, {target_idx}); } +void IntegrationField::integrate_cost(const std::shared_ptr &cost_field, + const std::shared_ptr &other, + sector_id_t other_sector_id, + const std::shared_ptr &portal) { + ENSURE(cost_field->get_size() == this->get_size(), + "cost field size " + << cost_field->get_size() << "x" << cost_field->get_size() + << " does not match integration field size " + << this->get_size() << "x" << this->get_size()); + ENSURE(other->get_size() == this->get_size(), + "other integration field size " + << other->get_size() << "x" << other->get_size() + << " does not match integration field size " + << this->get_size() << "x" << this->get_size()); + + // Get the cost of the cells on the entry side (other) of the portal + std::vector other_cells; + auto other_start = portal->get_entry_start(other_sector_id); + auto other_end = portal->get_entry_end(other_sector_id); + for (auto y = other_start.se; y <= other_end.se; ++y) { + for (auto x = other_start.ne; x <= other_end.ne; ++x) { + other_cells.push_back(x + y * this->size); + } + } + + // Integrate the cost of the cells on the exit side (this) of the portal + std::vector start_cells; + auto exit_start = portal->get_exit_start(other_sector_id); + auto exit_end = portal->get_exit_end(other_sector_id); + for (auto y = exit_start.se; y <= exit_end.se; ++y) { + for (auto x = exit_start.ne; x <= exit_end.ne; ++x) { + auto idx = x + y * this->size; + start_cells.push_back(idx); + + // Integrate the cells on this side of the portal + this->cells[idx].cost = other->get_cell(x + y * this->size).cost + cost_field->get_cost(idx); + + // TODO: Transfer flags from other to this + } + } + + // Integrate the rest of the cost field + this->integrate_cost(cost_field, std::move(start_cells)); +} + void IntegrationField::integrate_cost(const std::shared_ptr &cost_field, std::vector &&start_cells) { // Lookup table for cells that are in the open list diff --git a/libopenage/pathfinding/integration_field.h b/libopenage/pathfinding/integration_field.h index 3e01275d9d..02bce34b31 100644 --- a/libopenage/pathfinding/integration_field.h +++ b/libopenage/pathfinding/integration_field.h @@ -18,6 +18,7 @@ struct tile; namespace path { class CostField; +class Portal; /** * Integration field in the flow-field pathfinding algorithm. @@ -73,11 +74,26 @@ class IntegrationField { * * @param cost_field Cost field to integrate. * @param target Coordinates of the target cell. - * @param start_cells Cells flagged as "wavefront blocked" from a LOS pass. */ void integrate_cost(const std::shared_ptr &cost_field, const coord::tile &target); + /** + * Calculate the cost integration field starting from a portal to another + * integration field. + * + * The other integration field must already be integrated. + * + * @param cost_field Cost field to integrate. + * @param other Other integration field. + * @param other_sector_id Sector ID of the other integration field. + * @param portal Portal connecting the two fields. + */ + void integrate_cost(const std::shared_ptr &cost_field, + const std::shared_ptr &other, + sector_id_t other_sector_id, + const std::shared_ptr &portal); + /** * Calculate the cost integration field starting from a wavefront. * @@ -85,7 +101,7 @@ class IntegrationField { * @param start_cells Cells flagged as "wavefront blocked" from a LOS pass. */ void integrate_cost(const std::shared_ptr &cost_field, - std::vector &&start_cells = {}); + std::vector &&start_cells); /** * Get the integration field values. diff --git a/libopenage/pathfinding/pathfinder.cpp b/libopenage/pathfinding/pathfinder.cpp index 22fa2b0534..a70392f054 100644 --- a/libopenage/pathfinding/pathfinder.cpp +++ b/libopenage/pathfinding/pathfinder.cpp @@ -17,8 +17,25 @@ Pathfinder::Pathfinder() : } const Path Pathfinder::get_path(PathRequest &request) { + // High-level pathfinding + // Find the portals to use to get from the start to the target auto portal_path = this->portal_a_star(request); + // Low-level pathfinding + // Find the path within the sectors + auto grid = this->grids.at(request.grid_id); + auto sector_size = grid->get_sector_size(); + + auto target_sector_x = request.target.ne / sector_size; + auto target_sector_y = request.target.se / sector_size; + auto target_sector = grid->get_sector(target_sector_x, target_sector_y); + + auto target_x = request.target.ne % sector_size; + auto target_y = request.target.se % sector_size; + auto target = coord::tile{target_x, target_y}; + + auto flow_field = this->integrator->build(target_sector->get_cost_field(), target); + // TODO: Implement the rest of the pathfinding process return Path{}; } From e701d8c636d543e81daed9fd80785013bd64e2ed Mon Sep 17 00:00:00 2001 From: heinezen Date: Tue, 2 Apr 2024 23:43:53 +0200 Subject: [PATCH 079/112] path: More fine-grained control in integrator. --- libopenage/pathfinding/integrator.cpp | 37 +++++++++++++++++---- libopenage/pathfinding/integrator.h | 48 ++++++++++++++++++++++++--- libopenage/pathfinding/tests.cpp | 2 +- 3 files changed, 74 insertions(+), 13 deletions(-) diff --git a/libopenage/pathfinding/integrator.cpp b/libopenage/pathfinding/integrator.cpp index 9c5d6020e0..2d773bf735 100644 --- a/libopenage/pathfinding/integrator.cpp +++ b/libopenage/pathfinding/integrator.cpp @@ -9,16 +9,39 @@ namespace openage::path { -std::shared_ptr Integrator::build(const std::shared_ptr &cost_field, - const coord::tile &target) { - auto flow_field = std::make_shared(cost_field->get_size()); - auto integrate_field = std::make_shared(cost_field->get_size()); +std::shared_ptr Integrator::integrate(const std::shared_ptr &cost_field, + const coord::tile &target) { + auto integration_field = std::make_shared(cost_field->get_size()); + + auto wavefront_blocked = integration_field->integrate_los(cost_field, target); + integration_field->integrate_cost(cost_field, std::move(wavefront_blocked)); + + return integration_field; +} + +std::shared_ptr Integrator::integrate(const std::shared_ptr &cost_field, + const std::shared_ptr &other, + sector_id_t other_sector_id, + const std::shared_ptr &portal) { + auto integration_field = std::make_shared(cost_field->get_size()); + integration_field->integrate_cost(cost_field, other, other_sector_id, portal); - auto wavefront_blocked = integrate_field->integrate_los(cost_field, target); - integrate_field->integrate_cost(cost_field, std::move(wavefront_blocked)); - flow_field->build(integrate_field); + return integration_field; +} + +std::shared_ptr Integrator::build(const std::shared_ptr &integration_field) { + auto flow_field = std::make_shared(integration_field->get_size()); + flow_field->build(integration_field); return flow_field; } +Integrator::build_return_t Integrator::build(const std::shared_ptr &cost_field, + const coord::tile &target) { + auto integration_field = this->integrate(cost_field, target); + auto flow_field = this->build(integration_field); + + return std::make_pair(integration_field, flow_field); +} + } // namespace openage::path diff --git a/libopenage/pathfinding/integrator.h b/libopenage/pathfinding/integrator.h index 2af2bf1e0c..807c25a13c 100644 --- a/libopenage/pathfinding/integrator.h +++ b/libopenage/pathfinding/integrator.h @@ -5,6 +5,8 @@ #include #include +#include "pathfinding/types.h" + namespace openage { namespace coord { @@ -14,6 +16,8 @@ struct tile; namespace path { class CostField; class FlowField; +class IntegrationField; +class Portal; /** * Integrator for the flow field pathfinding algorithm. @@ -24,18 +28,52 @@ class Integrator { ~Integrator() = default; /** - * Build the flow field for a target. + * Integrate the cost field for a target. * * @param cost_field Cost field. * @param target Coordinates of the target cell. * + * @return Integration field. + */ + std::shared_ptr integrate(const std::shared_ptr &cost_field, + const coord::tile &target); + + /** + * Integrate the cost field from a portal. + * + * @param cost_field Cost field. + * @param other Integration field from the other side of the portal. + * @param other_sector_id Sector ID of the other side of the portal. + * @param portal Portal. + * + * @return Integration field. + */ + std::shared_ptr integrate(const std::shared_ptr &cost_field, + const std::shared_ptr &other, + sector_id_t other_sector_id, + const std::shared_ptr &portal); + + /** + * Build the flow field from an integration field. + * + * @param integration_field Integration field. + * * @return Flow field. */ - std::shared_ptr build(const std::shared_ptr &cost_field, - const coord::tile &target); + std::shared_ptr build(const std::shared_ptr &integration_field); + + using build_return_t = std::pair, std::shared_ptr>; -private: - // TODO: Cache field results. + /** + * Build the flow field for a target. + * + * @param cost_field Cost field. + * @param target Coordinates of the target cell. + * + * @return Flow field. + */ + build_return_t build(const std::shared_ptr &cost_field, + const coord::tile &target); }; } // namespace path diff --git a/libopenage/pathfinding/tests.cpp b/libopenage/pathfinding/tests.cpp index ea30a3d2a5..2c9775ca36 100644 --- a/libopenage/pathfinding/tests.cpp +++ b/libopenage/pathfinding/tests.cpp @@ -85,7 +85,7 @@ void flow_field() { auto integrator = std::make_shared(); // Build the flow field - auto flow_field = integrator->build(cost_field, coord::tile{2, 2}); + auto flow_field = integrator->build(cost_field, coord::tile{2, 2}).second; auto ff_cells = flow_field->get_cells(); // The flow field for targeting (2, 2) hould look like this: From 1466aa1c2412f9afc37ff7ce24d05793cb9dcce2 Mon Sep 17 00:00:00 2001 From: heinezen Date: Wed, 3 Apr 2024 21:59:33 +0200 Subject: [PATCH 080/112] path: Build flow field via portal. (partial) --- libopenage/pathfinding/flow_field.cpp | 38 ++++++++++++++++++++++----- libopenage/pathfinding/flow_field.h | 14 ++++++++++ 2 files changed, 45 insertions(+), 7 deletions(-) diff --git a/libopenage/pathfinding/flow_field.cpp b/libopenage/pathfinding/flow_field.cpp index 96c1a0cd56..a85a4d6769 100644 --- a/libopenage/pathfinding/flow_field.cpp +++ b/libopenage/pathfinding/flow_field.cpp @@ -8,6 +8,7 @@ #include "coord/tile.h" #include "pathfinding/definitions.h" #include "pathfinding/integration_field.h" +#include "pathfinding/portal.h" namespace openage::path { @@ -18,10 +19,10 @@ FlowField::FlowField(size_t size) : log::log(DBG << "Created flow field with size " << this->size << "x" << this->size); } -FlowField::FlowField(const std::shared_ptr &integrate_field) : - size{integrate_field->get_size()}, +FlowField::FlowField(const std::shared_ptr &integration_field) : + size{integration_field->get_size()}, cells(this->size * this->size, FLOW_INIT) { - this->build(integrate_field); + this->build(integration_field); } size_t FlowField::get_size() const { @@ -36,14 +37,14 @@ flow_dir_t FlowField::get_dir(const coord::tile &pos) const { return static_cast(this->get_cell(pos) & FLOW_DIR_MASK); } -void FlowField::build(const std::shared_ptr &integrate_field) { - ENSURE(integrate_field->get_size() == this->get_size(), +void FlowField::build(const std::shared_ptr &integration_field) { + ENSURE(integration_field->get_size() == this->get_size(), "integration field size " - << integrate_field->get_size() << "x" << integrate_field->get_size() + << integration_field->get_size() << "x" << integration_field->get_size() << " does not match flow field size " << this->get_size() << "x" << this->get_size()); - auto &integrate_cells = integrate_field->get_cells(); + auto &integrate_cells = integration_field->get_cells(); auto &flow_cells = this->cells; for (size_t y = 0; y < this->size; ++y) { @@ -139,6 +140,29 @@ void FlowField::build(const std::shared_ptr &integrate_field) } } +void FlowField::build(const std::shared_ptr &integration_field, + const std::shared_ptr &other, + sector_id_t other_sector_id, + const std::shared_ptr &portal) { + ENSURE(integration_field->get_size() == this->get_size(), + "integration field size " + << integration_field->get_size() << "x" << integration_field->get_size() + << " does not match flow field size " + << this->get_size() << "x" << this->get_size()); + + auto &integrate_cells = integration_field->get_cells(); + auto &flow_cells = this->cells; + auto direction = portal->get_direction(); + + if (direction == PortalDirection::NORTH_SOUTH) { + } + else if (direction == PortalDirection::EAST_WEST) { + } + else { + throw Error(ERR << "Invalid portal direction"); + } +} + const std::vector &FlowField::get_cells() const { return this->cells; } diff --git a/libopenage/pathfinding/flow_field.h b/libopenage/pathfinding/flow_field.h index 8389b2a0f7..32ebdee6b1 100644 --- a/libopenage/pathfinding/flow_field.h +++ b/libopenage/pathfinding/flow_field.h @@ -17,6 +17,7 @@ struct tile; namespace path { class IntegrationField; +class Portal; class FlowField { public: @@ -66,6 +67,19 @@ class FlowField { */ void build(const std::shared_ptr &integrate_field); + /** + * Build the flow field for a portal. + * + * @param integrate_field Integration field. + * @param other Other integration field. + * @param other_sector_id Sector ID of the other field. + * @param portal Portal connecting the two fields. + */ + void build(const std::shared_ptr &integrate_field, + const std::shared_ptr &other, + sector_id_t other_sector_id, + const std::shared_ptr &portal); + /** * Get the flow field values. * From b74d19cd1ecaca40e4997388a4c98f98e4cb8a41 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 7 Apr 2024 20:26:07 +0200 Subject: [PATCH 081/112] etc: Pretty printers for path::flow_t and path::integrated_flags_t. --- etc/gdb_pretty/printers.py | 84 +++++++++++++++++++++++++++ libopenage/pathfinding/flow_field.cpp | 1 - libopenage/pathfinding/types.h | 8 +-- 3 files changed, 88 insertions(+), 5 deletions(-) diff --git a/etc/gdb_pretty/printers.py b/etc/gdb_pretty/printers.py index ca8f92083c..cdf1e15858 100644 --- a/etc/gdb_pretty/printers.py +++ b/etc/gdb_pretty/printers.py @@ -279,6 +279,90 @@ def children(self): yield ('time', self.__val['time']) yield ('value', self.__val['value']) + +@printer_typedef('openage::path::flow_t') +class PathFlowTypePrinter: + """ + Pretty printer for openage::path::flow_t. + + TODO: Inherit from gdb.ValuePrinter when gdb 14.1 is available in all distros. + """ + + FLOW_FLAGS = { + 0x10: 'PATHABLE', + 0x20: 'LOS', + 0x40: 'WAVEFRONT_BLOCKED', + 0x80: 'UNUSED', + } + + FLOW_DIRECTION = { + 0x00: 'NORTH', + 0x01: 'NORTHEAST', + 0x02: 'EAST', + 0x03: 'SOUTHEAST', + 0x04: 'SOUTH', + 0x05: 'SOUTHWEST', + 0x06: 'WEST', + 0x07: 'NORTHWEST', + } + + def __init__(self, val: gdb.Value): + self.__val = val + + def to_string(self): + """ + Get the flow type as a string. + """ + flow = int(self.__val) + flags = flow & 0xF0 + direction = flow & 0x0F + return (f"{self.FLOW_DIRECTION.get(direction, 'INVALID')} | (" + f"{', '.join([self.FLOW_FLAGS[f] for f in self.FLOW_FLAGS if f & flags])})") + + def children(self): + """ + Get the displayed children of the flow type. + """ + flow = int(self.__val) + flags = flow & 0xF0 + direction = flow & 0x0F + yield ('direction', self.FLOW_DIRECTION[direction]) + for mask, flag in self.FLOW_FLAGS.items(): + yield (flag, bool(flags & mask)) + + +@printer_typedef('openage::path::integrated_flags_t') +class PathIntegratedFlagsTypePrinter: + """ + Pretty printer for openage::path::integrated_flags_t. + + TODO: Inherit from gdb.ValuePrinter when gdb 14.1 is available in all distros. + """ + + INTEGRATED_FLAGS = { + 0x01: 'LOS', + 0x02: 'WAVEFRONT_BLOCKED', + } + + def __init__(self, val: gdb.Value): + self.__val = val + + def to_string(self): + """ + Get the integrate type as a string. + """ + integrate = int(self.__val) + return f"{', '.join([self.INTEGRATED_FLAGS[f] for f in self.INTEGRATED_FLAGS if f & integrate])}" + + def children(self): + """ + Get the displayed children of the integrate type. + """ + integrate = int(self.__val) + for mask, flag in self.INTEGRATED_FLAGS.items(): + yield (flag, bool(integrate & mask)) + + # TODO: curve types # TODO: pathfinding types # TODO: input event codes diff --git a/libopenage/pathfinding/flow_field.cpp b/libopenage/pathfinding/flow_field.cpp index a85a4d6769..1eee7132f5 100644 --- a/libopenage/pathfinding/flow_field.cpp +++ b/libopenage/pathfinding/flow_field.cpp @@ -6,7 +6,6 @@ #include "log/log.h" #include "coord/tile.h" -#include "pathfinding/definitions.h" #include "pathfinding/integration_field.h" #include "pathfinding/portal.h" diff --git a/libopenage/pathfinding/types.h b/libopenage/pathfinding/types.h index 6ed735e896..57a52b9783 100644 --- a/libopenage/pathfinding/types.h +++ b/libopenage/pathfinding/types.h @@ -48,10 +48,10 @@ struct integrate_t { /** * Flow field cell value. * - * Bit 0: Line of sight flag. - * Bit 1: Pathable flag. - * Bit 2: Wavefront blocked flag. - * Bit 3: Unused. + * Bit 0: Unused. + * Bit 1: Wave front blocked flag. + * Bit 2: Line of sight flag. + * Bit 3: Pathable flag. * Bits 4-7: flow direction. */ using flow_t = uint8_t; From 555d19934e291315c4691f2f73e66da78755d107 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 7 Apr 2024 20:31:33 +0200 Subject: [PATCH 082/112] patg: Rename integrate_t to integrated_t. --- libopenage/pathfinding/definitions.h | 2 +- libopenage/pathfinding/integration_field.cpp | 6 +++--- libopenage/pathfinding/integration_field.h | 8 ++++---- libopenage/pathfinding/types.h | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/libopenage/pathfinding/definitions.h b/libopenage/pathfinding/definitions.h index 3d8584616b..cd12b6fe10 100644 --- a/libopenage/pathfinding/definitions.h +++ b/libopenage/pathfinding/definitions.h @@ -55,7 +55,7 @@ constexpr integrated_flags_t INTEGRATE_WAVEFRONT_BLOCKED_MASK = 0x02; /** * Initial value for a cell in the integration grid. */ -constexpr integrate_t INTEGRATE_INIT = {INTEGRATED_COST_UNREACHABLE, 0}; +constexpr integrated_t INTEGRATE_INIT = {INTEGRATED_COST_UNREACHABLE, 0}; /** diff --git a/libopenage/pathfinding/integration_field.cpp b/libopenage/pathfinding/integration_field.cpp index 9ff6e0854a..7e03bde8d8 100644 --- a/libopenage/pathfinding/integration_field.cpp +++ b/libopenage/pathfinding/integration_field.cpp @@ -25,11 +25,11 @@ size_t IntegrationField::get_size() const { return this->size; } -const integrate_t &IntegrationField::get_cell(const coord::tile &pos) const { +const integrated_t &IntegrationField::get_cell(const coord::tile &pos) const { return this->cells.at(pos.ne + pos.se * this->size); } -const integrate_t &IntegrationField::get_cell(size_t idx) const { +const integrated_t &IntegrationField::get_cell(size_t idx) const { return this->cells.at(idx); } @@ -266,7 +266,7 @@ void IntegrationField::integrate_cost(const std::shared_ptr &cost_fie } } -const std::vector &IntegrationField::get_cells() const { +const std::vector &IntegrationField::get_cells() const { return this->cells; } diff --git a/libopenage/pathfinding/integration_field.h b/libopenage/pathfinding/integration_field.h index 02bce34b31..07f3ece839 100644 --- a/libopenage/pathfinding/integration_field.h +++ b/libopenage/pathfinding/integration_field.h @@ -45,7 +45,7 @@ class IntegrationField { * @param pos Coordinates of the cell. * @return Integration value at the specified position. */ - const integrate_t &get_cell(const coord::tile &pos) const; + const integrated_t &get_cell(const coord::tile &pos) const; /** * Get the integration value at a specified position. @@ -53,7 +53,7 @@ class IntegrationField { * @param idx Index of the cell. * @return Integration value at the specified position. */ - const integrate_t &get_cell(size_t idx) const; + const integrated_t &get_cell(size_t idx) const; /** * Calculate the line-of-sight integration flags for a target cell. @@ -108,7 +108,7 @@ class IntegrationField { * * @return Integration field values. */ - const std::vector &get_cells() const; + const std::vector &get_cells() const; /** * Reset the integration field for a new integration. @@ -172,7 +172,7 @@ class IntegrationField { /** * Integration field values. */ - std::vector cells; + std::vector cells; }; } // namespace path diff --git a/libopenage/pathfinding/types.h b/libopenage/pathfinding/types.h index 57a52b9783..f543f269a4 100644 --- a/libopenage/pathfinding/types.h +++ b/libopenage/pathfinding/types.h @@ -30,7 +30,7 @@ using integrated_flags_t = uint8_t; /** * Integration field cell value. */ -struct integrate_t { +struct integrated_t { /** * Total integrated cost. */ From 6e19321b8bba126a2b4e20aa77941f851ddaec55 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 7 Apr 2024 21:08:07 +0200 Subject: [PATCH 083/112] etc: Pretty printer for path::integrated_t. --- etc/gdb_pretty/printers.py | 64 ++++++++++++++++++++++++++++++++------ 1 file changed, 55 insertions(+), 9 deletions(-) diff --git a/etc/gdb_pretty/printers.py b/etc/gdb_pretty/printers.py index cdf1e15858..53dd9cb8ef 100644 --- a/etc/gdb_pretty/printers.py +++ b/etc/gdb_pretty/printers.py @@ -288,14 +288,14 @@ class PathFlowTypePrinter: TODO: Inherit from gdb.ValuePrinter when gdb 14.1 is available in all distros. """ - FLOW_FLAGS = { + FLOW_FLAGS: dict = { 0x10: 'PATHABLE', 0x20: 'LOS', 0x40: 'WAVEFRONT_BLOCKED', 0x80: 'UNUSED', } - FLOW_DIRECTION = { + FLOW_DIRECTION: dict = { 0x00: 'NORTH', 0x01: 'NORTHEAST', 0x02: 'EAST', @@ -316,7 +316,7 @@ def to_string(self): flow = int(self.__val) flags = flow & 0xF0 direction = flow & 0x0F - return (f"{self.FLOW_DIRECTION.get(direction, 'INVALID')} | (" + return (f"{self.FLOW_DIRECTION.get(direction, 'INVALID')} (" f"{', '.join([self.FLOW_FLAGS[f] for f in self.FLOW_FLAGS if f & flags])})") def children(self): @@ -331,6 +331,28 @@ def children(self): yield (flag, bool(flags & mask)) +# Integrated flags +INTEGRATED_FLAGS: dict = { + 0x01: 'LOS', + 0x02: 'WAVEFRONT_BLOCKED', +} + + +def get_integrated_flags_list(value: int) -> str: + """ + Get the list of flags as a string. + + :param value: The value to get the flags for. + :type value: int + """ + flags = [] + for mask, flag in INTEGRATED_FLAGS.items(): + if value & mask: + flags.append(flag) + + return ' | '.join(flags) + + @printer_typedef('openage::path::integrated_flags_t') class PathIntegratedFlagsTypePrinter: """ @@ -339,11 +361,6 @@ class PathIntegratedFlagsTypePrinter: TODO: Inherit from gdb.ValuePrinter when gdb 14.1 is available in all distros. """ - INTEGRATED_FLAGS = { - 0x01: 'LOS', - 0x02: 'WAVEFRONT_BLOCKED', - } - def __init__(self, val: gdb.Value): self.__val = val @@ -352,7 +369,7 @@ def to_string(self): Get the integrate type as a string. """ integrate = int(self.__val) - return f"{', '.join([self.INTEGRATED_FLAGS[f] for f in self.INTEGRATED_FLAGS if f & integrate])}" + return get_integrated_flags_list(integrate) def children(self): """ @@ -363,6 +380,35 @@ def children(self): yield (flag, bool(integrate & mask)) +@printer_typedef('openage::path::integrated_t') +class PathIntegratedTypePrinter: + """ + Pretty printer for openage::path::integrated_t. + + TODO: Inherit from gdb.ValuePrinter when gdb 14.1 is available in all distros. + """ + + def __init__(self, val: gdb.Value): + self.__val = val + + def to_string(self): + """ + Get the integrate type as a string. + """ + output_str = f'cost = {self.__val["cost"]}' + flags = get_integrated_flags_list(int(self.__val['flags'])) + if len(flags) > 0: + output_str += f' ({flags})' + return output_str + + def children(self): + """ + Get the displayed children of the integrate type. + """ + yield ('cost', self.__val['cost']) + yield ('flags', self.__val['flags']) + + # TODO: curve types # TODO: pathfinding types # TODO: input event codes From 5c708eb336098f05fc985533889780011dd9d373 Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 8 Apr 2024 02:14:03 +0200 Subject: [PATCH 084/112] path: Don't use other integration field. Unnecessary because the flow fields will be the same. --- libopenage/pathfinding/integration_field.cpp | 24 +------------------- libopenage/pathfinding/integration_field.h | 4 ---- libopenage/pathfinding/integrator.cpp | 3 +-- libopenage/pathfinding/integrator.h | 2 -- libopenage/pathfinding/pathfinder.cpp | 4 ++-- 5 files changed, 4 insertions(+), 33 deletions(-) diff --git a/libopenage/pathfinding/integration_field.cpp b/libopenage/pathfinding/integration_field.cpp index 7e03bde8d8..5824bdd1e4 100644 --- a/libopenage/pathfinding/integration_field.cpp +++ b/libopenage/pathfinding/integration_field.cpp @@ -165,7 +165,6 @@ void IntegrationField::integrate_cost(const std::shared_ptr &cost_fie } void IntegrationField::integrate_cost(const std::shared_ptr &cost_field, - const std::shared_ptr &other, sector_id_t other_sector_id, const std::shared_ptr &portal) { ENSURE(cost_field->get_size() == this->get_size(), @@ -173,21 +172,6 @@ void IntegrationField::integrate_cost(const std::shared_ptr &cost_fie << cost_field->get_size() << "x" << cost_field->get_size() << " does not match integration field size " << this->get_size() << "x" << this->get_size()); - ENSURE(other->get_size() == this->get_size(), - "other integration field size " - << other->get_size() << "x" << other->get_size() - << " does not match integration field size " - << this->get_size() << "x" << this->get_size()); - - // Get the cost of the cells on the entry side (other) of the portal - std::vector other_cells; - auto other_start = portal->get_entry_start(other_sector_id); - auto other_end = portal->get_entry_end(other_sector_id); - for (auto y = other_start.se; y <= other_end.se; ++y) { - for (auto x = other_start.ne; x <= other_end.ne; ++x) { - other_cells.push_back(x + y * this->size); - } - } // Integrate the cost of the cells on the exit side (this) of the portal std::vector start_cells; @@ -195,13 +179,7 @@ void IntegrationField::integrate_cost(const std::shared_ptr &cost_fie auto exit_end = portal->get_exit_end(other_sector_id); for (auto y = exit_start.se; y <= exit_end.se; ++y) { for (auto x = exit_start.ne; x <= exit_end.ne; ++x) { - auto idx = x + y * this->size; - start_cells.push_back(idx); - - // Integrate the cells on this side of the portal - this->cells[idx].cost = other->get_cell(x + y * this->size).cost + cost_field->get_cost(idx); - - // TODO: Transfer flags from other to this + start_cells.push_back(x + y * this->size); } } diff --git a/libopenage/pathfinding/integration_field.h b/libopenage/pathfinding/integration_field.h index 07f3ece839..183fb9cff6 100644 --- a/libopenage/pathfinding/integration_field.h +++ b/libopenage/pathfinding/integration_field.h @@ -82,15 +82,11 @@ class IntegrationField { * Calculate the cost integration field starting from a portal to another * integration field. * - * The other integration field must already be integrated. - * * @param cost_field Cost field to integrate. - * @param other Other integration field. * @param other_sector_id Sector ID of the other integration field. * @param portal Portal connecting the two fields. */ void integrate_cost(const std::shared_ptr &cost_field, - const std::shared_ptr &other, sector_id_t other_sector_id, const std::shared_ptr &portal); diff --git a/libopenage/pathfinding/integrator.cpp b/libopenage/pathfinding/integrator.cpp index 2d773bf735..61c8cd033a 100644 --- a/libopenage/pathfinding/integrator.cpp +++ b/libopenage/pathfinding/integrator.cpp @@ -20,11 +20,10 @@ std::shared_ptr Integrator::integrate(const std::shared_ptr Integrator::integrate(const std::shared_ptr &cost_field, - const std::shared_ptr &other, sector_id_t other_sector_id, const std::shared_ptr &portal) { auto integration_field = std::make_shared(cost_field->get_size()); - integration_field->integrate_cost(cost_field, other, other_sector_id, portal); + integration_field->integrate_cost(cost_field, other_sector_id, portal); return integration_field; } diff --git a/libopenage/pathfinding/integrator.h b/libopenage/pathfinding/integrator.h index 807c25a13c..3c2f401c47 100644 --- a/libopenage/pathfinding/integrator.h +++ b/libopenage/pathfinding/integrator.h @@ -42,14 +42,12 @@ class Integrator { * Integrate the cost field from a portal. * * @param cost_field Cost field. - * @param other Integration field from the other side of the portal. * @param other_sector_id Sector ID of the other side of the portal. * @param portal Portal. * * @return Integration field. */ std::shared_ptr integrate(const std::shared_ptr &cost_field, - const std::shared_ptr &other, sector_id_t other_sector_id, const std::shared_ptr &portal); diff --git a/libopenage/pathfinding/pathfinder.cpp b/libopenage/pathfinding/pathfinder.cpp index a70392f054..68b3bc80b8 100644 --- a/libopenage/pathfinding/pathfinder.cpp +++ b/libopenage/pathfinding/pathfinder.cpp @@ -30,8 +30,8 @@ const Path Pathfinder::get_path(PathRequest &request) { auto target_sector_y = request.target.se / sector_size; auto target_sector = grid->get_sector(target_sector_x, target_sector_y); - auto target_x = request.target.ne % sector_size; - auto target_y = request.target.se % sector_size; + coord::tile_t target_x = request.target.ne % sector_size; + coord::tile_t target_y = request.target.se % sector_size; auto target = coord::tile{target_x, target_y}; auto flow_field = this->integrator->build(target_sector->get_cost_field(), target); From 9b4e7fbbda3655dcd18b316939b7b2cd54281520 Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 8 Apr 2024 02:23:18 +0200 Subject: [PATCH 085/112] etc: Fix pathfinding pretty printers flag lookups. --- etc/gdb_pretty/printers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/etc/gdb_pretty/printers.py b/etc/gdb_pretty/printers.py index 53dd9cb8ef..0dfac99af5 100644 --- a/etc/gdb_pretty/printers.py +++ b/etc/gdb_pretty/printers.py @@ -317,7 +317,7 @@ def to_string(self): flags = flow & 0xF0 direction = flow & 0x0F return (f"{self.FLOW_DIRECTION.get(direction, 'INVALID')} (" - f"{', '.join([self.FLOW_FLAGS[f] for f in self.FLOW_FLAGS if f & flags])})") + f"{', '.join([flag for mask, flag in self.FLOW_FLAGS.items() if mask & flags])})") def children(self): """ @@ -376,7 +376,7 @@ def children(self): Get the displayed children of the integrate type. """ integrate = int(self.__val) - for mask, flag in self.INTEGRATED_FLAGS.items(): + for mask, flag in INTEGRATED_FLAGS.items(): yield (flag, bool(integrate & mask)) From 479e64710e53a06f5c3179ce633fd7a6506da2fd Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 8 Apr 2024 02:30:01 +0200 Subject: [PATCH 086/112] path: Exit high-level pathfinding early if start and target are in same sector. --- libopenage/pathfinding/pathfinder.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/libopenage/pathfinding/pathfinder.cpp b/libopenage/pathfinding/pathfinder.cpp index 68b3bc80b8..759eb3f2a1 100644 --- a/libopenage/pathfinding/pathfinder.cpp +++ b/libopenage/pathfinding/pathfinder.cpp @@ -62,6 +62,11 @@ const std::vector> Pathfinder::portal_a_star(PathRequest auto target_sector_y = request.target.se / sector_size; auto target_sector = grid->get_sector(target_sector_x, target_sector_y); + if (start_sector == target_sector) { + // exit early if the start and target are in the same sector + return result; + } + // path node storage, always provides cheapest next node. heap_t node_candidates; From ae2785db05dc6cd4abedc1a399ef7ee6eb58b231 Mon Sep 17 00:00:00 2001 From: heinezen Date: Tue, 9 Apr 2024 23:48:06 +0200 Subject: [PATCH 087/112] path: Correctly set starting cost for portal target. --- libopenage/pathfinding/integration_field.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/libopenage/pathfinding/integration_field.cpp b/libopenage/pathfinding/integration_field.cpp index 5824bdd1e4..a38b06e67a 100644 --- a/libopenage/pathfinding/integration_field.cpp +++ b/libopenage/pathfinding/integration_field.cpp @@ -179,7 +179,12 @@ void IntegrationField::integrate_cost(const std::shared_ptr &cost_fie auto exit_end = portal->get_exit_end(other_sector_id); for (auto y = exit_start.se; y <= exit_end.se; ++y) { for (auto x = exit_start.ne; x <= exit_end.ne; ++x) { - start_cells.push_back(x + y * this->size); + // every portal cell is a target cell + auto target_idx = x + y * this->size; + + // Set the cost of all target cells to the start value + this->cells[target_idx].cost = INTEGRATED_COST_START; + start_cells.push_back(target_idx); } } From d93380ae7aa295dd8dc09fb9d18a731fe8bcf678 Mon Sep 17 00:00:00 2001 From: heinezen Date: Wed, 10 Apr 2024 00:36:20 +0200 Subject: [PATCH 088/112] path: Build flow field via portal. --- libopenage/pathfinding/flow_field.cpp | 52 ++++++++++++++++++++++++--- libopenage/pathfinding/flow_field.h | 14 ++++---- libopenage/pathfinding/integrator.cpp | 20 +++++++++++ libopenage/pathfinding/integrator.h | 30 +++++++++++++++- libopenage/pathfinding/pathfinder.cpp | 27 ++++++++++++-- 5 files changed, 129 insertions(+), 14 deletions(-) diff --git a/libopenage/pathfinding/flow_field.cpp b/libopenage/pathfinding/flow_field.cpp index 1eee7132f5..c649c13a29 100644 --- a/libopenage/pathfinding/flow_field.cpp +++ b/libopenage/pathfinding/flow_field.cpp @@ -71,7 +71,7 @@ void FlowField::build(const std::shared_ptr &integration_field } // Find the neighbor with the smallest cost. - flow_dir_t direction; + flow_dir_t direction = static_cast(this->cells[idx] & FLOW_DIR_MASK); auto smallest_cost = INTEGRATED_COST_UNREACHABLE; if (y > 0) { auto cost = integrate_cells[idx - this->size].cost; @@ -140,7 +140,7 @@ void FlowField::build(const std::shared_ptr &integration_field } void FlowField::build(const std::shared_ptr &integration_field, - const std::shared_ptr &other, + const std::shared_ptr & /* other */, sector_id_t other_sector_id, const std::shared_ptr &portal) { ENSURE(integration_field->get_size() == this->get_size(), @@ -149,17 +149,61 @@ void FlowField::build(const std::shared_ptr &integration_field << " does not match flow field size " << this->get_size() << "x" << this->get_size()); - auto &integrate_cells = integration_field->get_cells(); auto &flow_cells = this->cells; auto direction = portal->get_direction(); + // portal entry and exit cell coordinates + auto entry_start = portal->get_entry_start(other_sector_id); + auto entry_end = portal->get_entry_end(other_sector_id); + auto exit_start = portal->get_exit_start(other_sector_id); + + // TODO: Compare integration values from other side of portal + // auto &integrate_cells = integration_field->get_cells(); + + // set the direction for the flow field cells that are part of the portal if (direction == PortalDirection::NORTH_SOUTH) { + bool other_is_north = entry_start.se > exit_start.se; + if (other_is_north) { + auto y = entry_start.se; + for (auto x = entry_start.ne; x <= entry_end.ne; ++x) { + auto idx = y * this->size + x; + flow_cells[idx] = flow_cells[idx] | FLOW_PATHABLE_MASK; + flow_cells[idx] = flow_cells[idx] | static_cast(flow_dir_t::NORTH); + } + } + else { + auto y = entry_start.se; + for (auto x = entry_start.ne; x <= entry_end.ne; ++x) { + auto idx = y * this->size + x; + flow_cells[idx] = flow_cells[idx] | FLOW_PATHABLE_MASK; + flow_cells[idx] = flow_cells[idx] | static_cast(flow_dir_t::SOUTH); + } + } } else if (direction == PortalDirection::EAST_WEST) { + bool other_is_east = entry_start.ne > exit_start.ne; + if (other_is_east) { + auto x = entry_start.ne; + for (auto y = entry_start.se; y <= entry_end.se; ++y) { + auto idx = y * this->size + x; + flow_cells[idx] = flow_cells[idx] | FLOW_PATHABLE_MASK; + flow_cells[idx] = flow_cells[idx] | static_cast(flow_dir_t::EAST); + } + } + else { + auto x = entry_start.ne; + for (auto y = entry_start.se; y <= entry_end.se; ++y) { + auto idx = y * this->size + x; + flow_cells[idx] = flow_cells[idx] | FLOW_PATHABLE_MASK; + flow_cells[idx] = flow_cells[idx] | static_cast(flow_dir_t::WEST); + } + } } else { - throw Error(ERR << "Invalid portal direction"); + throw Error(ERR << "Invalid portal direction: " << static_cast(direction)); } + + this->build(integration_field); } const std::vector &FlowField::get_cells() const { diff --git a/libopenage/pathfinding/flow_field.h b/libopenage/pathfinding/flow_field.h index 32ebdee6b1..beb5bccf06 100644 --- a/libopenage/pathfinding/flow_field.h +++ b/libopenage/pathfinding/flow_field.h @@ -31,9 +31,9 @@ class FlowField { /** * Create a flow field from an existing integration field. * - * @param integrate_field Integration field. + * @param integration_field Integration field. */ - FlowField(const std::shared_ptr &integrate_field); + FlowField(const std::shared_ptr &integration_field); /** * Get the size of the flow field. @@ -63,19 +63,19 @@ class FlowField { /** * Build the flow field. * - * @param integrate_field Integration field. + * @param integration_field Integration field. */ - void build(const std::shared_ptr &integrate_field); + void build(const std::shared_ptr &integration_field); /** * Build the flow field for a portal. * - * @param integrate_field Integration field. - * @param other Other integration field. + * @param integration_field Integration field. + * @param other Integration field of the other sector. * @param other_sector_id Sector ID of the other field. * @param portal Portal connecting the two fields. */ - void build(const std::shared_ptr &integrate_field, + void build(const std::shared_ptr &integration_field, const std::shared_ptr &other, sector_id_t other_sector_id, const std::shared_ptr &portal); diff --git a/libopenage/pathfinding/integrator.cpp b/libopenage/pathfinding/integrator.cpp index 61c8cd033a..28f8a829ca 100644 --- a/libopenage/pathfinding/integrator.cpp +++ b/libopenage/pathfinding/integrator.cpp @@ -35,6 +35,16 @@ std::shared_ptr Integrator::build(const std::shared_ptr Integrator::build(const std::shared_ptr &integration_field, + const std::shared_ptr &other, + sector_id_t other_sector_id, + const std::shared_ptr &portal) { + auto flow_field = std::make_shared(integration_field->get_size()); + flow_field->build(integration_field, other, other_sector_id, portal); + + return flow_field; +} + Integrator::build_return_t Integrator::build(const std::shared_ptr &cost_field, const coord::tile &target) { auto integration_field = this->integrate(cost_field, target); @@ -43,4 +53,14 @@ Integrator::build_return_t Integrator::build(const std::shared_ptr &c return std::make_pair(integration_field, flow_field); } +Integrator::build_return_t Integrator::build(const std::shared_ptr &cost_field, + const std::shared_ptr &other_integration_field, + sector_id_t other_sector_id, + const std::shared_ptr &portal) { + auto integration_field = this->integrate(cost_field, other_sector_id, portal); + auto flow_field = this->build(integration_field, other_integration_field, other_sector_id, portal); + + return std::make_pair(integration_field, flow_field); +} + } // namespace openage::path diff --git a/libopenage/pathfinding/integrator.h b/libopenage/pathfinding/integrator.h index 3c2f401c47..2b44d3f18f 100644 --- a/libopenage/pathfinding/integrator.h +++ b/libopenage/pathfinding/integrator.h @@ -60,10 +60,25 @@ class Integrator { */ std::shared_ptr build(const std::shared_ptr &integration_field); + /** + * Build the flow field from a portal. + * + * @param integration_field Integration field. + * @param other Integration field of the other side of the portal. + * @param other_sector_id Sector ID of the other side of the portal. + * @param portal Portal. + * + * @return Flow field. + */ + std::shared_ptr build(const std::shared_ptr &integration_field, + const std::shared_ptr &other, + sector_id_t other_sector_id, + const std::shared_ptr &portal); + using build_return_t = std::pair, std::shared_ptr>; /** - * Build the flow field for a target. + * Build the integration field and flow field for a target. * * @param cost_field Cost field. * @param target Coordinates of the target cell. @@ -72,6 +87,19 @@ class Integrator { */ build_return_t build(const std::shared_ptr &cost_field, const coord::tile &target); + + /** + * Build the integration field and flow field from a portal. + * + * @param cost_field Cost field. + * @param other_integration_field Integration field of the other side of the portal. + * @param other_sector_id Sector ID of the other side of the portal. + * @param portal Portal. + */ + build_return_t build(const std::shared_ptr &cost_field, + const std::shared_ptr &other_integration_field, + sector_id_t other_sector_id, + const std::shared_ptr &portal); }; } // namespace path diff --git a/libopenage/pathfinding/pathfinder.cpp b/libopenage/pathfinding/pathfinder.cpp index 759eb3f2a1..b42f3471b2 100644 --- a/libopenage/pathfinding/pathfinder.cpp +++ b/libopenage/pathfinding/pathfinder.cpp @@ -34,10 +34,33 @@ const Path Pathfinder::get_path(PathRequest &request) { coord::tile_t target_y = request.target.se % sector_size; auto target = coord::tile{target_x, target_y}; - auto flow_field = this->integrator->build(target_sector->get_cost_field(), target); + auto sector_fields = this->integrator->build(target_sector->get_cost_field(), target); + auto prev_integration_field = sector_fields.first; + auto prev_sector_id = target_sector->get_id(); + + if (not portal_path.empty()) { + std::vector flow_fields; + flow_fields.reserve(portal_path.size() + 1); + + flow_fields.push_back(sector_fields); + for (auto &portal : portal_path) { + auto prev_sector = grid->get_sector(prev_sector_id); + auto next_sector_id = portal->get_exit_sector(prev_sector_id); + auto next_sector = grid->get_sector(next_sector_id); + + sector_fields = this->integrator->build(next_sector->get_cost_field(), + prev_integration_field, + prev_sector_id, + portal); + flow_fields.push_back(sector_fields); + + prev_integration_field = sector_fields.first; + prev_sector_id = next_sector_id; + } + } // TODO: Implement the rest of the pathfinding process - return Path{}; + return Path{request.grid_id, {}}; } const std::shared_ptr &Pathfinder::get_grid(grid_id_t id) const { From f7913587ed27db88634b1d553ce4bcc164601ae7 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sat, 13 Apr 2024 00:20:08 +0200 Subject: [PATCH 089/112] path: Exclude target cells from search in flow field. --- libopenage/pathfinding/flow_field.cpp | 37 ++++++++++++++++++--------- libopenage/pathfinding/flow_field.h | 6 ++++- 2 files changed, 30 insertions(+), 13 deletions(-) diff --git a/libopenage/pathfinding/flow_field.cpp b/libopenage/pathfinding/flow_field.cpp index c649c13a29..0aa8daf84b 100644 --- a/libopenage/pathfinding/flow_field.cpp +++ b/libopenage/pathfinding/flow_field.cpp @@ -36,7 +36,8 @@ flow_dir_t FlowField::get_dir(const coord::tile &pos) const { return static_cast(this->get_cell(pos) & FLOW_DIR_MASK); } -void FlowField::build(const std::shared_ptr &integration_field) { +void FlowField::build(const std::shared_ptr &integration_field, + const std::unordered_set &target_cells) { ENSURE(integration_field->get_size() == this->get_size(), "integration field size " << integration_field->get_size() << "x" << integration_field->get_size() @@ -50,6 +51,11 @@ void FlowField::build(const std::shared_ptr &integration_field for (size_t x = 0; x < this->size; ++x) { size_t idx = y * this->size + x; + if (target_cells.contains(idx)) { + // Ignore target cells + continue; + } + if (integrate_cells[idx].cost == INTEGRATED_COST_UNREACHABLE) { // Cell cannot be used as path continue; @@ -154,48 +160,55 @@ void FlowField::build(const std::shared_ptr &integration_field // portal entry and exit cell coordinates auto entry_start = portal->get_entry_start(other_sector_id); - auto entry_end = portal->get_entry_end(other_sector_id); auto exit_start = portal->get_exit_start(other_sector_id); + auto exit_end = portal->get_exit_end(other_sector_id); // TODO: Compare integration values from other side of portal // auto &integrate_cells = integration_field->get_cells(); + // cells that are part of the portal + std::unordered_set portal_cells; + // set the direction for the flow field cells that are part of the portal if (direction == PortalDirection::NORTH_SOUTH) { bool other_is_north = entry_start.se > exit_start.se; if (other_is_north) { - auto y = entry_start.se; - for (auto x = entry_start.ne; x <= entry_end.ne; ++x) { + auto y = exit_start.se; + for (auto x = exit_start.ne; x <= exit_end.ne; ++x) { auto idx = y * this->size + x; flow_cells[idx] = flow_cells[idx] | FLOW_PATHABLE_MASK; flow_cells[idx] = flow_cells[idx] | static_cast(flow_dir_t::NORTH); + portal_cells.insert(idx); } } else { - auto y = entry_start.se; - for (auto x = entry_start.ne; x <= entry_end.ne; ++x) { + auto y = exit_start.se; + for (auto x = exit_start.ne; x <= exit_end.ne; ++x) { auto idx = y * this->size + x; flow_cells[idx] = flow_cells[idx] | FLOW_PATHABLE_MASK; flow_cells[idx] = flow_cells[idx] | static_cast(flow_dir_t::SOUTH); + portal_cells.insert(idx); } } } else if (direction == PortalDirection::EAST_WEST) { - bool other_is_east = entry_start.ne > exit_start.ne; + bool other_is_east = entry_start.ne < exit_start.ne; if (other_is_east) { - auto x = entry_start.ne; - for (auto y = entry_start.se; y <= entry_end.se; ++y) { + auto x = exit_start.ne; + for (auto y = exit_start.se; y <= exit_end.se; ++y) { auto idx = y * this->size + x; flow_cells[idx] = flow_cells[idx] | FLOW_PATHABLE_MASK; flow_cells[idx] = flow_cells[idx] | static_cast(flow_dir_t::EAST); + portal_cells.insert(idx); } } else { - auto x = entry_start.ne; - for (auto y = entry_start.se; y <= entry_end.se; ++y) { + auto x = exit_start.ne; + for (auto y = exit_start.se; y <= exit_end.se; ++y) { auto idx = y * this->size + x; flow_cells[idx] = flow_cells[idx] | FLOW_PATHABLE_MASK; flow_cells[idx] = flow_cells[idx] | static_cast(flow_dir_t::WEST); + portal_cells.insert(idx); } } } @@ -203,7 +216,7 @@ void FlowField::build(const std::shared_ptr &integration_field throw Error(ERR << "Invalid portal direction: " << static_cast(direction)); } - this->build(integration_field); + this->build(integration_field, portal_cells); } const std::vector &FlowField::get_cells() const { diff --git a/libopenage/pathfinding/flow_field.h b/libopenage/pathfinding/flow_field.h index beb5bccf06..1c1adcfe33 100644 --- a/libopenage/pathfinding/flow_field.h +++ b/libopenage/pathfinding/flow_field.h @@ -4,6 +4,7 @@ #include #include +#include #include #include "pathfinding/definitions.h" @@ -64,8 +65,11 @@ class FlowField { * Build the flow field. * * @param integration_field Integration field. + * @param target_cells Target cells of the flow field. These cells are ignored + * when building the field. */ - void build(const std::shared_ptr &integration_field); + void build(const std::shared_ptr &integration_field, + const std::unordered_set &target_cells = {}); /** * Build the flow field for a portal. From bbb5ab30e127c6983f63aa9e6c79549724a43532 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sat, 13 Apr 2024 00:20:44 +0200 Subject: [PATCH 090/112] path: Traverse flow field to find waypoints. --- libopenage/pathfinding/pathfinder.cpp | 169 +++++++++++++++++++++++--- libopenage/pathfinding/pathfinder.h | 8 +- 2 files changed, 156 insertions(+), 21 deletions(-) diff --git a/libopenage/pathfinding/pathfinder.cpp b/libopenage/pathfinding/pathfinder.cpp index b42f3471b2..9515d8f8e5 100644 --- a/libopenage/pathfinding/pathfinder.cpp +++ b/libopenage/pathfinding/pathfinder.cpp @@ -3,6 +3,7 @@ #include "pathfinder.h" #include "coord/phys.h" +#include "pathfinding/flow_field.h" #include "pathfinding/grid.h" #include "pathfinding/integrator.h" #include "pathfinding/portal.h" @@ -16,7 +17,7 @@ Pathfinder::Pathfinder() : integrator{std::make_shared()} { } -const Path Pathfinder::get_path(PathRequest &request) { +const Path Pathfinder::get_path(const PathRequest &request) { // High-level pathfinding // Find the portals to use to get from the start to the target auto portal_path = this->portal_a_star(request); @@ -38,29 +39,33 @@ const Path Pathfinder::get_path(PathRequest &request) { auto prev_integration_field = sector_fields.first; auto prev_sector_id = target_sector->get_id(); - if (not portal_path.empty()) { - std::vector flow_fields; - flow_fields.reserve(portal_path.size() + 1); + std::vector waypoints; + std::vector>> flow_fields; + flow_fields.reserve(portal_path.size() + 1); - flow_fields.push_back(sector_fields); - for (auto &portal : portal_path) { - auto prev_sector = grid->get_sector(prev_sector_id); - auto next_sector_id = portal->get_exit_sector(prev_sector_id); - auto next_sector = grid->get_sector(next_sector_id); + flow_fields.push_back(std::make_pair(target_sector->get_id(), sector_fields.second)); + for (auto &portal : portal_path) { + auto prev_sector = grid->get_sector(prev_sector_id); + auto next_sector_id = portal->get_exit_sector(prev_sector_id); + auto next_sector = grid->get_sector(next_sector_id); - sector_fields = this->integrator->build(next_sector->get_cost_field(), - prev_integration_field, - prev_sector_id, - portal); - flow_fields.push_back(sector_fields); + sector_fields = this->integrator->build(next_sector->get_cost_field(), + prev_integration_field, + prev_sector_id, + portal); + flow_fields.push_back(std::make_pair(next_sector_id, sector_fields.second)); - prev_integration_field = sector_fields.first; - prev_sector_id = next_sector_id; - } + prev_integration_field = sector_fields.first; + prev_sector_id = next_sector_id; } + // reverse the flow fields so they are ordered from start to target + std::reverse(flow_fields.begin(), flow_fields.end()); + + waypoints = this->get_waypoints(flow_fields, request); + // TODO: Implement the rest of the pathfinding process - return Path{request.grid_id, {}}; + return Path{request.grid_id, waypoints}; } const std::shared_ptr &Pathfinder::get_grid(grid_id_t id) const { @@ -71,7 +76,7 @@ void Pathfinder::add_grid(const std::shared_ptr &grid) { this->grids[grid->get_id()] = grid; } -const std::vector> Pathfinder::portal_a_star(PathRequest &request) const { +const std::vector> Pathfinder::portal_a_star(const PathRequest &request) const { std::vector> result; auto grid = this->grids.at(request.grid_id); @@ -186,6 +191,132 @@ const std::vector> Pathfinder::portal_a_star(PathRequest return result; } +const std::vector Pathfinder::get_waypoints(const std::vector>> &flow_fields, + const PathRequest &request) const { + ENSURE(flow_fields.size() > 1, "At least 1 flow field is required for finding waypoints."); + + std::vector waypoints; + + auto grid = this->get_grid(request.grid_id); + auto sector_size = grid->get_sector_size(); + coord::tile_t start_x = request.start.ne % sector_size; + coord::tile_t start_y = request.start.se % sector_size; + coord::tile_t target_x = request.target.ne % sector_size; + coord::tile_t target_y = request.target.se % sector_size; + + coord::tile_t current_x = start_x; + coord::tile_t current_y = start_y; + flow_dir_t current_direction = flow_fields.at(0).second->get_dir(coord::tile{current_x, current_y}); + for (size_t i = 0; i < flow_fields.size(); ++i) { + auto sector = grid->get_sector(flow_fields.at(i).first); + auto flow_field = flow_fields.at(i).second; + bool target_field = i == flow_fields.size() - 1; + + // navigate the flow field vectors until we reach its edge (or the target) + while (current_x < sector_size and current_y < sector_size + and current_x >= 0 and current_y >= 0) { + auto cell = flow_field->get_cell(coord::tile{current_x, current_y}); + if (cell & FLOW_LOS_MASK) { + // check if we reached an LOS cell + auto sector_pos = sector->get_position(); + auto cell_pos = coord::tile{sector_pos.ne * sector_size, + sector_pos.se * sector_size} + + coord::tile_delta{current_x, current_y}; + waypoints.push_back(cell_pos); + break; + } + + // check if we need to change direction + auto cell_direction = flow_field->get_dir(coord::tile{current_x, current_y}); + if (cell_direction != current_direction) { + // add the current cell as a waypoint + auto sector_pos = sector->get_position(); + auto cell_pos = coord::tile{sector_pos.ne * sector_size, + sector_pos.se * sector_size} + + coord::tile_delta{current_x, current_y}; + waypoints.push_back(cell_pos); + current_direction = cell_direction; + } + + // move to the next cell + switch (current_direction) { + case flow_dir_t::NORTH: + current_y -= 1; + break; + case flow_dir_t::NORTH_EAST: + current_x += 1; + current_y -= 1; + break; + case flow_dir_t::EAST: + current_x += 1; + break; + case flow_dir_t::SOUTH_EAST: + current_x += 1; + current_y += 1; + break; + case flow_dir_t::SOUTH: + current_y += 1; + break; + case flow_dir_t::SOUTH_WEST: + current_x -= 1; + current_y += 1; + break; + case flow_dir_t::WEST: + current_x -= 1; + break; + case flow_dir_t::NORTH_WEST: + current_x -= 1; + current_y -= 1; + break; + default: + throw Error{ERR << "Invalid flow direction: " << static_cast(current_direction)}; + } + } + + // reset the current position for the next flow field + switch (current_direction) { + case flow_dir_t::NORTH: + current_y = sector_size - 1; + break; + case flow_dir_t::NORTH_EAST: + current_x = current_x + 1; + current_y = sector_size - 1; + break; + case flow_dir_t::EAST: + current_x = 0; + break; + case flow_dir_t::SOUTH_EAST: + current_x = 0; + current_y = current_y + 1; + break; + case flow_dir_t::SOUTH: + current_y = 0; + break; + case flow_dir_t::SOUTH_WEST: + current_x = current_x - 1; + current_y = 0; + break; + case flow_dir_t::WEST: + current_x = sector_size - 1; + break; + case flow_dir_t::NORTH_WEST: + current_x = sector_size - 1; + current_y = current_y - 1; + break; + default: + throw Error{ERR << "Invalid flow direction: " << static_cast(current_direction)}; + } + } + + // add the target position as the last waypoint + auto sector_pos = grid->get_sector(flow_fields.back().first)->get_position(); + auto target_pos = coord::tile{sector_pos.ne * sector_size, sector_pos.se * sector_size} + + coord::tile_delta{target_x, target_y}; + waypoints.push_back(target_pos); + + return waypoints; +} + int Pathfinder::heuristic_cost(const coord::tile &portal_pos, const coord::tile &target_pos) { auto portal_phys_pos = portal_pos.to_phys2(); diff --git a/libopenage/pathfinding/pathfinder.h b/libopenage/pathfinding/pathfinder.h index b89f994bc8..935a7db488 100644 --- a/libopenage/pathfinding/pathfinder.h +++ b/libopenage/pathfinding/pathfinder.h @@ -15,6 +15,7 @@ namespace openage::path { class Grid; class Integrator; class Portal; +class FlowField; /** * Pathfinder for flow field pathfinding. @@ -58,10 +59,13 @@ class Pathfinder { * * @return Path found by the pathfinder. */ - const Path get_path(PathRequest &request); + const Path get_path(const PathRequest &request); private: - const std::vector> portal_a_star(PathRequest &request) const; + const std::vector> portal_a_star(const PathRequest &request) const; + + const std::vector get_waypoints(const std::vector>> &flow_fields, + const PathRequest &request) const; /** * Calculate the heuristic cost between a portal and a target cell. From 8fff7012f20ce850006db68013c0bf5e95d706a3 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sat, 13 Apr 2024 00:21:03 +0200 Subject: [PATCH 091/112] path: Show waypoints in demo 1. --- libopenage/pathfinding/demo/demo_1.cpp | 64 ++++++++++++++++++++++++-- libopenage/pathfinding/demo/demo_1.h | 8 ++++ 2 files changed, 67 insertions(+), 5 deletions(-) diff --git a/libopenage/pathfinding/demo/demo_1.cpp b/libopenage/pathfinding/demo/demo_1.cpp index c2f5ba19de..a8eb68f00c 100644 --- a/libopenage/pathfinding/demo/demo_1.cpp +++ b/libopenage/pathfinding/demo/demo_1.cpp @@ -75,6 +75,12 @@ void path_demo_1(const util::Path &path) { auto pathfinder = std::make_shared(); pathfinder->add_grid(grid); + // Render the grid + auto qtapp = std::make_shared(); + auto window = std::make_shared("openage pathfinding test", 1024, 768); + auto render_manager = std::make_shared(qtapp, window, path, grid); + log::log(INFO << "Created render manager for pathfinding demo"); + auto path_request = path::PathRequest{ 0, coord::tile{2, 26}, @@ -82,11 +88,7 @@ void path_demo_1(const util::Path &path) { }; auto path_result = pathfinder->get_path(path_request); - // Render the grid - auto qtapp = std::make_shared(); - auto window = std::make_shared("openage pathfinding test", 1024, 768); - auto render_manager = std::make_shared(qtapp, window, path, grid); - log::log(INFO << "Created render manager for pathfinding demo"); + render_manager->create_waypoint_tiles(path_result); render_manager->run(); } @@ -477,4 +479,56 @@ void RenderManager1::create_portal_tiles(const std::shared_ptr &grid } } +void RenderManager1::create_waypoint_tiles(const Path &path) { + auto width = grid->get_size()[0]; + auto height = grid->get_size()[1]; + auto sector_size = grid->get_sector_size(); + + float tile_offset_width = 2.0f / (width * sector_size); + float tile_offset_height = 2.0f / (height * sector_size); + + for (auto &tile : path.waypoints) { + std::array tile_data{ + -1.0f + tile.ne * tile_offset_width, + 1.0f - tile.se * tile_offset_height, + -1.0f + tile.ne * tile_offset_width, + 1.0f - (tile.se + 1) * tile_offset_height, + -1.0f + (tile.ne + 1) * tile_offset_width, + 1.0f - tile.se * tile_offset_height, + -1.0f + (tile.ne + 1) * tile_offset_width, + 1.0f - (tile.se + 1) * tile_offset_height, + }; + + renderer::resources::VertexInputInfo info{ + {renderer::resources::vertex_input_t::V2F32}, + renderer::resources::vertex_layout_t::AOS, + renderer::resources::vertex_primitive_t::TRIANGLE_STRIP}; + + auto const data_size = tile_data.size() * sizeof(float); + std::vector verts(data_size); + std::memcpy(verts.data(), tile_data.data(), data_size); + + auto tile_mesh = renderer::resources::MeshData(std::move(verts), info); + auto tile_geometry = renderer->add_mesh_geometry(tile_mesh); + + Eigen::Matrix4f id_matrix = Eigen::Matrix4f::Identity(); + auto tile_unifs = obj_shader->new_uniform_input( + "color", + Eigen::Vector4f{1.0, 0.0, 0.0, 1.0}, + "model", + id_matrix, + "view", + id_matrix, + "proj", + id_matrix); + auto tile_obj = renderer::Renderable{ + tile_unifs, + tile_geometry, + true, + true, + }; + this->field_pass->add_renderables({tile_obj}); + } +} + } // namespace openage::path::tests diff --git a/libopenage/pathfinding/demo/demo_1.h b/libopenage/pathfinding/demo/demo_1.h index 52ed7d1f65..eafcc3d45f 100644 --- a/libopenage/pathfinding/demo/demo_1.h +++ b/libopenage/pathfinding/demo/demo_1.h @@ -3,6 +3,7 @@ #pragma once #include "pathfinding/definitions.h" +#include "pathfinding/path.h" #include "renderer/resources/mesh_data.h" #include "util/path.h" @@ -62,6 +63,13 @@ class RenderManager1 { */ void run(); + /** + * Create renderables for the waypoint tiles of a path. + * + * @param path Path object. + */ + void create_waypoint_tiles(const Path &path); + private: /** * Load the shader sources for the demo and create the shader programs. From 6f568b1d8d354ac3d34ebe96b209327a8c1fbdb0 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sat, 13 Apr 2024 00:25:12 +0200 Subject: [PATCH 092/112] path: Include path start in waypoints. --- libopenage/pathfinding/path.h | 2 ++ libopenage/pathfinding/pathfinder.cpp | 7 ++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/libopenage/pathfinding/path.h b/libopenage/pathfinding/path.h index cb08e09991..8d64f5914f 100644 --- a/libopenage/pathfinding/path.h +++ b/libopenage/pathfinding/path.h @@ -28,6 +28,8 @@ struct Path { // ID of the grid to used for pathfinding. size_t grid_id; // Waypoints of the path. + // First waypoint is the start position of the path request. + // Last waypoint is the target position of the path request. std::vector waypoints; }; diff --git a/libopenage/pathfinding/pathfinder.cpp b/libopenage/pathfinding/pathfinder.cpp index 9515d8f8e5..2568f59f87 100644 --- a/libopenage/pathfinding/pathfinder.cpp +++ b/libopenage/pathfinding/pathfinder.cpp @@ -39,7 +39,6 @@ const Path Pathfinder::get_path(const PathRequest &request) { auto prev_integration_field = sector_fields.first; auto prev_sector_id = target_sector->get_id(); - std::vector waypoints; std::vector>> flow_fields; flow_fields.reserve(portal_path.size() + 1); @@ -62,9 +61,11 @@ const Path Pathfinder::get_path(const PathRequest &request) { // reverse the flow fields so they are ordered from start to target std::reverse(flow_fields.begin(), flow_fields.end()); - waypoints = this->get_waypoints(flow_fields, request); + // traverse the flow fields to get the waypoints + std::vector waypoints{request.start}; + auto flow_field_waypoints = this->get_waypoints(flow_fields, request); + waypoints.insert(waypoints.end(), flow_field_waypoints.begin(), flow_field_waypoints.end()); - // TODO: Implement the rest of the pathfinding process return Path{request.grid_id, waypoints}; } From 07de19bab8845a62de2782a7f32fa757c647e99e Mon Sep 17 00:00:00 2001 From: heinezen Date: Sat, 13 Apr 2024 01:03:43 +0200 Subject: [PATCH 093/112] path: Draw start and end of path with separate colors. --- libopenage/pathfinding/demo/demo_1.cpp | 91 +++++++++++++++++++++++++- 1 file changed, 88 insertions(+), 3 deletions(-) diff --git a/libopenage/pathfinding/demo/demo_1.cpp b/libopenage/pathfinding/demo/demo_1.cpp index a8eb68f00c..8811634c95 100644 --- a/libopenage/pathfinding/demo/demo_1.cpp +++ b/libopenage/pathfinding/demo/demo_1.cpp @@ -487,7 +487,11 @@ void RenderManager1::create_waypoint_tiles(const Path &path) { float tile_offset_width = 2.0f / (width * sector_size); float tile_offset_height = 2.0f / (height * sector_size); - for (auto &tile : path.waypoints) { + Eigen::Matrix4f id_matrix = Eigen::Matrix4f::Identity(); + + // Draw in-between waypoints + for (size_t i = 1; i < path.waypoints.size() - 1; i++) { + auto tile = path.waypoints[i]; std::array tile_data{ -1.0f + tile.ne * tile_offset_width, 1.0f - tile.se * tile_offset_height, @@ -511,10 +515,9 @@ void RenderManager1::create_waypoint_tiles(const Path &path) { auto tile_mesh = renderer::resources::MeshData(std::move(verts), info); auto tile_geometry = renderer->add_mesh_geometry(tile_mesh); - Eigen::Matrix4f id_matrix = Eigen::Matrix4f::Identity(); auto tile_unifs = obj_shader->new_uniform_input( "color", - Eigen::Vector4f{1.0, 0.0, 0.0, 1.0}, + Eigen::Vector4f{0.0, 0.25, 1.0, 1.0}, "model", id_matrix, "view", @@ -529,6 +532,88 @@ void RenderManager1::create_waypoint_tiles(const Path &path) { }; this->field_pass->add_renderables({tile_obj}); } + + // Draw start and end waypoints with different colors + auto start_tile = path.waypoints.front(); + std::array start_tile_data{ + -1.0f + start_tile.ne * tile_offset_width, + 1.0f - start_tile.se * tile_offset_height, + -1.0f + start_tile.ne * tile_offset_width, + 1.0f - (start_tile.se + 1) * tile_offset_height, + -1.0f + (start_tile.ne + 1) * tile_offset_width, + 1.0f - start_tile.se * tile_offset_height, + -1.0f + (start_tile.ne + 1) * tile_offset_width, + 1.0f - (start_tile.se + 1) * tile_offset_height, + }; + + renderer::resources::VertexInputInfo start_info{ + {renderer::resources::vertex_input_t::V2F32}, + renderer::resources::vertex_layout_t::AOS, + renderer::resources::vertex_primitive_t::TRIANGLE_STRIP}; + + auto const start_data_size = start_tile_data.size() * sizeof(float); + std::vector start_verts(start_data_size); + std::memcpy(start_verts.data(), start_tile_data.data(), start_data_size); + + auto start_tile_mesh = renderer::resources::MeshData(std::move(start_verts), start_info); + auto start_tile_geometry = renderer->add_mesh_geometry(start_tile_mesh); + auto start_tile_unifs = obj_shader->new_uniform_input( + "color", + Eigen::Vector4f{0.0, 0.5, 0.0, 1.0}, + "model", + id_matrix, + "view", + id_matrix, + "proj", + id_matrix); + auto start_tile_obj = renderer::Renderable{ + start_tile_unifs, + start_tile_geometry, + true, + true, + }; + + auto end_tile = path.waypoints.back(); + std::array end_tile_data{ + -1.0f + end_tile.ne * tile_offset_width, + 1.0f - end_tile.se * tile_offset_height, + -1.0f + end_tile.ne * tile_offset_width, + 1.0f - (end_tile.se + 1) * tile_offset_height, + -1.0f + (end_tile.ne + 1) * tile_offset_width, + 1.0f - end_tile.se * tile_offset_height, + -1.0f + (end_tile.ne + 1) * tile_offset_width, + 1.0f - (end_tile.se + 1) * tile_offset_height, + }; + + renderer::resources::VertexInputInfo end_info{ + {renderer::resources::vertex_input_t::V2F32}, + renderer::resources::vertex_layout_t::AOS, + renderer::resources::vertex_primitive_t::TRIANGLE_STRIP}; + + auto const end_data_size = end_tile_data.size() * sizeof(float); + std::vector end_verts(end_data_size); + std::memcpy(end_verts.data(), end_tile_data.data(), end_data_size); + + auto end_tile_mesh = renderer::resources::MeshData(std::move(end_verts), end_info); + auto end_tile_geometry = renderer->add_mesh_geometry(end_tile_mesh); + auto end_tile_unifs = obj_shader->new_uniform_input( + "color", + Eigen::Vector4f{1.0, 0.5, 0.0, 1.0}, + "model", + id_matrix, + "view", + id_matrix, + "proj", + id_matrix); + + auto end_tile_obj = renderer::Renderable{ + end_tile_unifs, + end_tile_geometry, + true, + true, + }; + + this->field_pass->add_renderables({start_tile_obj, end_tile_obj}); } } // namespace openage::path::tests From 17b53aa83e13842dbe06d9fb7843ae4e6b01b8e3 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sat, 13 Apr 2024 01:10:54 +0200 Subject: [PATCH 094/112] path: Make some of the variable types in the demo more obvious. --- libopenage/pathfinding/demo/demo_1.cpp | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/libopenage/pathfinding/demo/demo_1.cpp b/libopenage/pathfinding/demo/demo_1.cpp index 8811634c95..1472590535 100644 --- a/libopenage/pathfinding/demo/demo_1.cpp +++ b/libopenage/pathfinding/demo/demo_1.cpp @@ -23,15 +23,15 @@ void path_demo_1(const util::Path &path) { auto grid = std::make_shared(0, util::Vector2s{4, 3}, 10); // Initialize the cost field for each sector. - for (auto sector : grid->get_sectors()) { + for (auto §or : grid->get_sectors()) { auto cost_field = sector->get_cost_field(); - auto sector_cost = sectors_cost.at(sector->get_id()); + std::vector sector_cost = sectors_cost.at(sector->get_id()); cost_field->set_costs(std::move(sector_cost)); } // Initialize portals between sectors. - auto grid_size = grid->get_size(); - auto portal_id = 0; + util::Vector2s grid_size = grid->get_size(); + portal_id_t portal_id = 0; for (size_t y = 0; y < grid_size[1]; y++) { for (size_t x = 0; x < grid_size[0]; x++) { auto sector = grid->get_sector(x, y); @@ -39,7 +39,7 @@ void path_demo_1(const util::Path &path) { if (x < grid_size[0] - 1) { auto neighbor = grid->get_sector(x + 1, y); auto portals = sector->find_portals(neighbor, PortalDirection::EAST_WEST, portal_id); - for (auto portal : portals) { + for (auto &portal : portals) { sector->add_portal(portal); neighbor->add_portal(portal); } @@ -48,7 +48,7 @@ void path_demo_1(const util::Path &path) { if (y < grid_size[1] - 1) { auto neighbor = grid->get_sector(x, y + 1); auto portals = sector->find_portals(neighbor, PortalDirection::NORTH_SOUTH, portal_id); - for (auto portal : portals) { + for (auto &portal : portals) { sector->add_portal(portal); neighbor->add_portal(portal); } @@ -81,12 +81,13 @@ void path_demo_1(const util::Path &path) { auto render_manager = std::make_shared(qtapp, window, path, grid); log::log(INFO << "Created render manager for pathfinding demo"); - auto path_request = path::PathRequest{ + // TODO: Make the path request interactive with window callbacks + PathRequest path_request{ 0, coord::tile{2, 26}, coord::tile{36, 2}, }; - auto path_result = pathfinder->get_path(path_request); + Path path_result = pathfinder->get_path(path_request); render_manager->create_waypoint_tiles(path_result); From 1d384a6d315a07b329aebe784aedd862fe505c17 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sat, 13 Apr 2024 01:23:05 +0200 Subject: [PATCH 095/112] path: Fix warnings about type conversions. --- libopenage/pathfinding/demo/demo_1.cpp | 21 ++++++++++++-------- libopenage/pathfinding/integration_field.cpp | 4 ++-- libopenage/pathfinding/pathfinder.cpp | 7 ++++--- 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/libopenage/pathfinding/demo/demo_1.cpp b/libopenage/pathfinding/demo/demo_1.cpp index 1472590535..752854a3e5 100644 --- a/libopenage/pathfinding/demo/demo_1.cpp +++ b/libopenage/pathfinding/demo/demo_1.cpp @@ -72,15 +72,11 @@ void path_demo_1(const util::Path &path) { } } + // Create a pathfinder for searching paths on the grid auto pathfinder = std::make_shared(); pathfinder->add_grid(grid); - // Render the grid - auto qtapp = std::make_shared(); - auto window = std::make_shared("openage pathfinding test", 1024, 768); - auto render_manager = std::make_shared(qtapp, window, path, grid); - log::log(INFO << "Created render manager for pathfinding demo"); - + // Create a path request and get the path // TODO: Make the path request interactive with window callbacks PathRequest path_request{ 0, @@ -89,8 +85,17 @@ void path_demo_1(const util::Path &path) { }; Path path_result = pathfinder->get_path(path_request); + // Create a renderer to display the grid and path + auto qtapp = std::make_shared(); + auto window = std::make_shared("openage pathfinding test", 1024, 768); + auto render_manager = std::make_shared(qtapp, window, path, grid); + log::log(INFO << "Created render manager for pathfinding demo"); + + // Create renderables for the waypoints of the path render_manager->create_waypoint_tiles(path_result); + // Run the renderer pss to draw the grid and path into a window + // TODO: Make this a while (not window.should_close()) loop render_manager->run(); } @@ -423,13 +428,13 @@ void RenderManager1::create_portal_tiles(const std::shared_ptr &grid std::vector tiles; if (direction == PortalDirection::NORTH_SOUTH) { auto y = start.se; - for (size_t x = start.ne; x <= end.ne; ++x) { + for (auto x = start.ne; x <= end.ne; ++x) { tiles.push_back(coord::tile{x, y}); } } else { auto x = start.ne; - for (size_t y = start.se; y <= end.se; ++y) { + for (auto y = start.se; y <= end.se; ++y) { tiles.push_back(coord::tile{x, y}); } } diff --git a/libopenage/pathfinding/integration_field.cpp b/libopenage/pathfinding/integration_field.cpp index a38b06e67a..0deed51073 100644 --- a/libopenage/pathfinding/integration_field.cpp +++ b/libopenage/pathfinding/integration_field.cpp @@ -311,10 +311,10 @@ std::vector> IntegrationField::get_los_corners(const std::sh if (blocker.ne > 0) { left_cost = cost_field->get_cost(coord::tile{blocker.ne - 1, blocker.se}); } - if (blocker.se < this->size - 1) { + if (static_cast(blocker.se) < this->size - 1) { bottom_cost = cost_field->get_cost(coord::tile{blocker.ne, blocker.se + 1}); } - if (blocker.ne < this->size - 1) { + if (static_cast(blocker.ne) < this->size - 1) { right_cost = cost_field->get_cost(coord::tile{blocker.ne + 1, blocker.se}); } diff --git a/libopenage/pathfinding/pathfinder.cpp b/libopenage/pathfinding/pathfinder.cpp index 2568f59f87..3f3c7fb24f 100644 --- a/libopenage/pathfinding/pathfinder.cpp +++ b/libopenage/pathfinding/pathfinder.cpp @@ -211,11 +211,12 @@ const std::vector Pathfinder::get_waypoints(const std::vectorget_sector(flow_fields.at(i).first); auto flow_field = flow_fields.at(i).second; - bool target_field = i == flow_fields.size() - 1; // navigate the flow field vectors until we reach its edge (or the target) - while (current_x < sector_size and current_y < sector_size - and current_x >= 0 and current_y >= 0) { + while (current_x < static_cast(sector_size) + and current_y < static_cast(sector_size) + and current_x >= 0 + and current_y >= 0) { auto cell = flow_field->get_cell(coord::tile{current_x, current_y}); if (cell & FLOW_LOS_MASK) { // check if we reached an LOS cell From ed5d6c834df572df064e42b3afe5614225ef388c Mon Sep 17 00:00:00 2001 From: heinezen Date: Sat, 13 Apr 2024 01:34:58 +0200 Subject: [PATCH 096/112] path: Initialize portals in Grid object. --- libopenage/pathfinding/grid.cpp | 34 +++++++++++++++++++++++++++++++++ libopenage/pathfinding/grid.h | 7 +++++++ 2 files changed, 41 insertions(+) diff --git a/libopenage/pathfinding/grid.cpp b/libopenage/pathfinding/grid.cpp index 51940d2bf3..4100492c16 100644 --- a/libopenage/pathfinding/grid.cpp +++ b/libopenage/pathfinding/grid.cpp @@ -65,4 +65,38 @@ const std::vector> &Grid::get_sectors() const { return this->sectors; } +void Grid::init_portals() { + // Create portals between neighboring sectors. + portal_id_t portal_id = 0; + for (size_t y = 0; y < this->size[1]; y++) { + for (size_t x = 0; x < this->size[0]; x++) { + auto sector = this->get_sector(x, y); + + if (x < this->size[0] - 1) { + auto neighbor = this->get_sector(x + 1, y); + auto portals = sector->find_portals(neighbor, PortalDirection::EAST_WEST, portal_id); + for (auto &portal : portals) { + sector->add_portal(portal); + neighbor->add_portal(portal); + } + portal_id += portals.size(); + } + if (y < this->size[1] - 1) { + auto neighbor = this->get_sector(x, y + 1); + auto portals = sector->find_portals(neighbor, PortalDirection::NORTH_SOUTH, portal_id); + for (auto &portal : portals) { + sector->add_portal(portal); + neighbor->add_portal(portal); + } + portal_id += portals.size(); + } + } + } + + // Connect mutually reachable exits of sectors. + for (auto §or : this->sectors) { + sector->connect_exits(); + } +} + } // namespace openage::path diff --git a/libopenage/pathfinding/grid.h b/libopenage/pathfinding/grid.h index c5e783ddfa..83b4c2b721 100644 --- a/libopenage/pathfinding/grid.h +++ b/libopenage/pathfinding/grid.h @@ -88,6 +88,13 @@ class Grid { */ const std::vector> &get_sectors() const; + /** + * Initialize the portals of the sectors on the grid. + * + * This should be called after all sectors' cost fields have been initialized. + */ + void init_portals(); + private: /** * ID of the grid. From 9cc8710d546f3b2965378ed91fcbb35fdd8935fb Mon Sep 17 00:00:00 2001 From: heinezen Date: Sat, 13 Apr 2024 01:51:26 +0200 Subject: [PATCH 097/112] path: Move flow_dir_t to types.h. --- libopenage/pathfinding/definitions.h | 16 ---------------- libopenage/pathfinding/types.h | 16 ++++++++++++++++ 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/libopenage/pathfinding/definitions.h b/libopenage/pathfinding/definitions.h index cd12b6fe10..d61c5fe43c 100644 --- a/libopenage/pathfinding/definitions.h +++ b/libopenage/pathfinding/definitions.h @@ -58,22 +58,6 @@ constexpr integrated_flags_t INTEGRATE_WAVEFRONT_BLOCKED_MASK = 0x02; constexpr integrated_t INTEGRATE_INIT = {INTEGRATED_COST_UNREACHABLE, 0}; -/** - * Flow field direction types. - * - * Encoded into the flow_t values. - */ -enum class flow_dir_t : uint8_t { - NORTH = 0x00, - NORTH_EAST = 0x01, - EAST = 0x02, - SOUTH_EAST = 0x03, - SOUTH = 0x04, - SOUTH_WEST = 0x05, - WEST = 0x06, - NORTH_WEST = 0x07, -}; - /** * Initial value for a flow field cell. */ diff --git a/libopenage/pathfinding/types.h b/libopenage/pathfinding/types.h index f543f269a4..1d0623328e 100644 --- a/libopenage/pathfinding/types.h +++ b/libopenage/pathfinding/types.h @@ -45,6 +45,22 @@ struct integrated_t { integrated_flags_t flags; }; +/** + * Flow field direction types. + * + * Encoded into the flow_t values. + */ +enum class flow_dir_t : uint8_t { + NORTH = 0x00, + NORTH_EAST = 0x01, + EAST = 0x02, + SOUTH_EAST = 0x03, + SOUTH = 0x04, + SOUTH_WEST = 0x05, + WEST = 0x06, + NORTH_WEST = 0x07, +}; + /** * Flow field cell value. * From 1f882f0fd05c715cd51578dc9193ebb489907612 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sat, 13 Apr 2024 02:08:15 +0200 Subject: [PATCH 098/112] path: Document pathfinding functions. --- libopenage/pathfinding/pathfinder.h | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/libopenage/pathfinding/pathfinder.h b/libopenage/pathfinding/pathfinder.h index 935a7db488..e6e43a7abf 100644 --- a/libopenage/pathfinding/pathfinder.h +++ b/libopenage/pathfinding/pathfinder.h @@ -62,8 +62,23 @@ class Pathfinder { const Path get_path(const PathRequest &request); private: + /** + * High-level pathfinder. Uses A* to find the path through the portals of sectors. + * + * @param request Pathfinding request. + * + * @return Portals to traverse in order to reach the target. + */ const std::vector> portal_a_star(const PathRequest &request) const; + /** + * Low-level pathfinder. Uses flow fields to find the path through the sectors. + * + * @param flow_fields Flow fields for the sectors. + * @param request Pathfinding request. + * + * @return Waypoint coordinates to traverse in order to reach the target. + */ const std::vector get_waypoints(const std::vector>> &flow_fields, const PathRequest &request) const; From 78657a3e8678bab62495b79e88777a133ab7cc57 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sat, 13 Apr 2024 02:48:23 +0200 Subject: [PATCH 099/112] path: Add timer for path request to demo 1. --- libopenage/pathfinding/demo/demo_1.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/libopenage/pathfinding/demo/demo_1.cpp b/libopenage/pathfinding/demo/demo_1.cpp index 752854a3e5..d8da5193e8 100644 --- a/libopenage/pathfinding/demo/demo_1.cpp +++ b/libopenage/pathfinding/demo/demo_1.cpp @@ -8,6 +8,7 @@ #include "pathfinding/pathfinder.h" #include "pathfinding/portal.h" #include "pathfinding/sector.h" +#include "util/timer.h" #include "renderer/gui/integration/public/gui_application_with_logger.h" #include "renderer/opengl/window.h" @@ -76,6 +77,8 @@ void path_demo_1(const util::Path &path) { auto pathfinder = std::make_shared(); pathfinder->add_grid(grid); + util::Timer timer; + // Create a path request and get the path // TODO: Make the path request interactive with window callbacks PathRequest path_request{ @@ -83,7 +86,11 @@ void path_demo_1(const util::Path &path) { coord::tile{2, 26}, coord::tile{36, 2}, }; + timer.start(); Path path_result = pathfinder->get_path(path_request); + timer.stop(); + + log::log(INFO << "Pathfinding took " << timer.getval() / 1000 << " ps"); // Create a renderer to display the grid and path auto qtapp = std::make_shared(); From 9ff58024f659ac39a8dfd4f3fae7479fba051f0d Mon Sep 17 00:00:00 2001 From: heinezen Date: Sat, 13 Apr 2024 02:48:43 +0200 Subject: [PATCH 100/112] path: Add TODOs. --- libopenage/pathfinding/integration_field.cpp | 2 ++ libopenage/pathfinding/integrator.h | 3 +++ libopenage/pathfinding/portal.h | 4 ++++ libopenage/pathfinding/sector.cpp | 1 + libopenage/pathfinding/types.h | 2 ++ 5 files changed, 12 insertions(+) diff --git a/libopenage/pathfinding/integration_field.cpp b/libopenage/pathfinding/integration_field.cpp index 0deed51073..201823eec4 100644 --- a/libopenage/pathfinding/integration_field.cpp +++ b/libopenage/pathfinding/integration_field.cpp @@ -185,6 +185,8 @@ void IntegrationField::integrate_cost(const std::shared_ptr &cost_fie // Set the cost of all target cells to the start value this->cells[target_idx].cost = INTEGRATED_COST_START; start_cells.push_back(target_idx); + + // TODO: Transfer flags and cost from the other integration field } } diff --git a/libopenage/pathfinding/integrator.h b/libopenage/pathfinding/integrator.h index 2b44d3f18f..027f627274 100644 --- a/libopenage/pathfinding/integrator.h +++ b/libopenage/pathfinding/integrator.h @@ -100,6 +100,9 @@ class Integrator { const std::shared_ptr &other_integration_field, sector_id_t other_sector_id, const std::shared_ptr &portal); + +private: + // TODO: Cache created flow fields. }; } // namespace path diff --git a/libopenage/pathfinding/portal.h b/libopenage/pathfinding/portal.h index 60a6bf9e48..2487e449c7 100644 --- a/libopenage/pathfinding/portal.h +++ b/libopenage/pathfinding/portal.h @@ -212,11 +212,15 @@ class Portal { /** * Exits in sector 0 reachable from the portal. + * + * TODO: Also store avarage cost to reach each exit. */ std::vector> sector0_exits; /** * Exits in sector 1 reachable from the portal. + * + * TODO: Also store avarage cost to reach each exit. */ std::vector> sector1_exits; diff --git a/libopenage/pathfinding/sector.cpp b/libopenage/pathfinding/sector.cpp index bc75894cb8..8dcdfb0e5a 100644 --- a/libopenage/pathfinding/sector.cpp +++ b/libopenage/pathfinding/sector.cpp @@ -197,6 +197,7 @@ void Sector::connect_exits() { neighbors.clear(); // mark the current cell as visited + // TODO: Record the cost of reaching this cell visited.insert(current); } diff --git a/libopenage/pathfinding/types.h b/libopenage/pathfinding/types.h index 1d0623328e..11190f6551 100644 --- a/libopenage/pathfinding/types.h +++ b/libopenage/pathfinding/types.h @@ -11,6 +11,8 @@ namespace openage::path { /** * Movement cost in the cost field. * + * TODO: Cost stamps + * * 0: uninitialized * 1-254: normal cost * 255: impassable From e9470220473de22973efa23568bb6fa586bfa56f Mon Sep 17 00:00:00 2001 From: heinezen Date: Sat, 13 Apr 2024 20:42:03 +0200 Subject: [PATCH 101/112] path: Fix an issue with gcc not default initializing correctly in release build. --- libopenage/pathfinding/sector.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libopenage/pathfinding/sector.cpp b/libopenage/pathfinding/sector.cpp index 8dcdfb0e5a..6b56114e47 100644 --- a/libopenage/pathfinding/sector.cpp +++ b/libopenage/pathfinding/sector.cpp @@ -57,9 +57,9 @@ std::vector> Sector::find_portals(const std::shared_ptr< auto other_cost = other->get_cost_field(); // compare the edges of the sectors - size_t start; - size_t end; - bool passable_edge; + size_t start = 0; + size_t end = 0; + bool passable_edge = false; for (size_t i = 0; i < this->cost_field->get_size(); ++i) { auto coord_this = coord::tile{0, 0}; auto coord_other = coord::tile{0, 0}; From 066dd06abef0b30fee21eeed8697c300a73b1aed Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 14 Apr 2024 01:27:40 +0200 Subject: [PATCH 102/112] doc: Pathfinding documentaton text. --- doc/code/pathfinding/README.md | 122 +++++++++++++++++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 doc/code/pathfinding/README.md diff --git a/doc/code/pathfinding/README.md b/doc/code/pathfinding/README.md new file mode 100644 index 0000000000..f7c5ce6d5b --- /dev/null +++ b/doc/code/pathfinding/README.md @@ -0,0 +1,122 @@ +# Pathfinding + +openage's pathfinding subsystem implements structures used for navigating game entities in the +game world. These structures define movement costs for a map and allow search for a path +from one coordinate in the game world to another. + +Pathfinding is a subsystem of the [game simulation](/doc/code/game_simulation/README.md) where +it is primarily used for movement and placement of game entities. + +1. [Architecture](#architecture) +2. [Workflow](#workflow) + + +## Architecture + +The architecture of the pathfinder is heavily based on the article *Crowd Pathfinding and Steering* +*Using Flow Field Tiles* by Elijah Emerson (available [here](http://www.gameaipro.com/GameAIPro/GameAIPro_Chapter23_Crowd_Pathfinding_and_Steering_Using_Flow_Field_Tiles.pdf)). A core design +decision taken from this article was the usage of flow fields as the basis for the pathing algorithm. + +Flow fields offer a few advantages for large group movements, i.e. the movement you typically see in +RTS games like Age of Empires. For example, they allow reusing already computed path results for +subsequent pathing requests and can also be used for smart collision avoidance. One downside +of using flow fields can be the heavy upfront cost of pathing calculations. However, the downsides +can be mitigated to some degree with caching and high-level optimizations. + +The openage pathfinder is tile-based, like most flow field pathfinding implementations. Every tile +has a movement cost associated with it that represents the cost of a game entity moving on that tile. +When computing a path from A to B, the pathfinder tries to find the sequence of tiles with the cheapest +accumulated movement cost. + +In openage, the pathfinding subsystem is independent from the terrain implementation in the game +simulation. Terrain data may be used to influence movement cost during gameplay, but pathfinding +can be initialized without any requirements for terrain code. By only loosely connecting these two system, +we have a much more fine-grained control that allows for better optimization of the pathfinder. +The most relevant similarity between terrain and pathfinding code is that they use the same +[coordinate systems](/doc/code/coordinate-systems.md#tiletile3). To make the distinction between +pathfinding and terrain more clear, we use the term *cells* for tiles in the pathfinder. + +![UML pathfinding classes]() + +The relationship between classes in the pathfinder can be seen above. `Grid` is the top-level structure +for storage of movement cost. There may be multiple grids defined, one for each movement type +used by game entities in the game simulation. + +Every grid is subdivided into sectors, represented by the `Sector` class. Each sectors holds a +pointer to a `CostField` which stores the movement cost of individual cells. All sectors on the +same grid have a fixed square size that is determined by the grid. Thus, the sector size on +a grid is always consistent but different grid may utilize different sector sizes. + +Sectors on a grid are connect to each other with so-called portals, see the `Portal` +class. Portals represent a passable gateway between two sectors where game entities +can pass through. As such, portals store the coordinates of the cells in each sector +where game entities can pass. `Portal` objects are created for every continuous sequence of cells +on the edges between two sectors where the cells are passable on both sides of the two sectors. +Therefore, there may be multiple portals defined for the edge between two sectors. + +Additionally, each portal stores which other portals it can reach in a specific sector. The +result is a portal "graph" that can be searched separately to determine which portals +and sectors are visited for a specific pathing request. This is used in the high-level +pathfinder to preselect the sectors that flow fields need to be generated for (see the +[Workflow section](#workflow)). + +The individual movement cost of each cell in a sector are recorded in a `CostField` object. +The cost of a cell can range from 1 (minimum cost) to 254 (maximum cost), while a cost of 255 +makes a cell impassible. For Age of Empires, usually only the minimum and impassable cost +values are relevant. The cost field is built when the grid is first initialized and +individual cost of cells can be altered during gameplay events. + +To get a path between two coordinates, the game simulation mainly interfaces with a `Pathfinder` +object. The `Pathfinder` class calls the actual pathfinding algorithms used for searching +for a path and stores references to all available grids in the pathfinding subsystems. It can receive +`PathRequest` objects that contain information about the grid that should be searched as well as +the start and target coordinates of the desired path. Found paths are returned as `Path` objects +which store the waypoint coordinates for the computed path. + +Flow field calculations are controlled by an `Integrator` object, which process a cost field +from a sector as well as target coordinates to compute an `IntegrationField` and a `FlowField` object. +`IntegrationField`s are intermediary objects that store the accumulated cost of reaching +the target cell for each other cell in the field, using the cell values in the cost field as basis. +From the integration field, a `FlowField` object is created. Cells in the flow field store +movement vectors that point to their next cheapest neighbor cell in the integration field. `Path`s +may be computed from these flow field by simply following the movement vectors until the +target coordinate is reached. + + +## Workflow + +To initiate a new search, the pathfinder receives a `PathRequest` object, e.g. from the game simulation. +Every path request contains the following information: + +- ID of the grid to search +- start cell coordinates +- target cell coordinates + +The actual pathfinding algorithm is split into two stages. + +1. High-level portal-based search to identify the visited sectors on the grid +2. Low-level flow field-based search in the identified sectors to find the waypoints for the path + +The high-level search is accomplished by utilizing the portal graph, i.e. the +connections between portals in each sector. From the portal graph, a node mesh is created +that can be searched with a graph-traversal algorithm. For this purpose, the A\* algorithm +is used. The result of the high-level search is a list of sectors and portals that are +traversed by the path. + +The high-level search is mainly motivated by the need to avoid costly flow field +calculations on the whole grid. As the portal graph should already be precomputed when +a path request is made, the main influence on performance is the A\* algorithm. Given +a limited number of portals, the A\* search should overall be very cheap. + +The resulting list of sectors and portals is subsequently used in the low-level flow +field calculations. As a first step, the pathfinder uses its integrator to generate +a flow field for each identified sector. Generation starts with the target sector +and ends with the start sector. Flow field results are passed through at the cells +of the identified portals to make the flow between sectors seamless. + + + +In a second step, the pathfinder follows the movement vectors in the flow fields from +the start cell to the target cell. Waypoints are created for every direction change, so +that game entities can travel in straight lines between them. The list of waypoints +from start to target is then returned by the pathfinder via a `Path` object. From f9f9ae1af3ae4fd872e5b78ddcf851dc9b2245c8 Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 6 May 2024 20:15:17 +0200 Subject: [PATCH 103/112] docs: Pathfinding documentation diagrams. --- doc/code/images/pathfinder_architecture.svg | 175 +++++++++++++ doc/code/images/pathfinder_architecture.uxf | 271 ++++++++++++++++++++ doc/code/pathfinding/README.md | 2 +- 3 files changed, 447 insertions(+), 1 deletion(-) create mode 100644 doc/code/images/pathfinder_architecture.svg create mode 100644 doc/code/images/pathfinder_architecture.uxf diff --git a/doc/code/images/pathfinder_architecture.svg b/doc/code/images/pathfinder_architecture.svg new file mode 100644 index 0000000000..2046a1a486 --- /dev/null +++ b/doc/code/images/pathfinder_architecture.svg @@ -0,0 +1,175 @@ + + +GamestateMovement,collision, etc.PathfinderPathFlowFieldIntegrationFieldIntegratorPathfinderPortalCostFieldSectorGrid diff --git a/doc/code/images/pathfinder_architecture.uxf b/doc/code/images/pathfinder_architecture.uxf new file mode 100644 index 0000000000..3a051b2acf --- /dev/null +++ b/doc/code/images/pathfinder_architecture.uxf @@ -0,0 +1,271 @@ + + + 10 + + UMLClass + + 710 + 510 + 120 + 60 + + +*Grid* + + + + UMLClass + + 710 + 620 + 120 + 60 + + +*Sector* + + + + UMLClass + + 630 + 730 + 120 + 60 + + +*CostField* + + + + UMLClass + + 790 + 730 + 120 + 60 + + +*Portal* + + + + UMLClass + + 710 + 400 + 120 + 60 + + +*Pathfinder* + + + + UMLClass + + 980 + 400 + 120 + 60 + + +*Integrator* + + + + UMLClass + + 1040 + 490 + 160 + 60 + + +*IntegrationField* + + + + UMLClass + + 1040 + 560 + 120 + 60 + + +*FlowField* + + + + UMLClass + + 520 + 400 + 120 + 60 + + +*Path* + + + + Relation + + 760 + 450 + 30 + 80 + + lt=<. + 10.0;60.0;10.0;10.0 + + + Relation + + 760 + 560 + 30 + 80 + + lt=<. + 10.0;60.0;10.0;10.0 + + + Relation + + 720 + 670 + 30 + 80 + + lt=<. + 10.0;60.0;10.0;10.0 + + + Relation + + 800 + 670 + 30 + 80 + + lt=<. + 10.0;60.0;10.0;10.0 + + + Relation + + 820 + 420 + 180 + 30 + + lt=<. + 160.0;10.0;10.0;10.0 + + + Relation + + 990 + 450 + 30 + 160 + + lt=. + 10.0;140.0;10.0;10.0 + + + Relation + + 990 + 510 + 70 + 30 + + lt=<. + 50.0;10.0;10.0;10.0 + + + Relation + + 990 + 580 + 70 + 30 + + lt=<. + 50.0;10.0;10.0;10.0 + + + Relation + + 630 + 420 + 100 + 30 + + lt=<. + 10.0;10.0;80.0;10.0 + + + Relation + + 760 + 200 + 30 + 220 + + lt=<. + 10.0;200.0;10.0;10.0 + + + Text + + 460 + 330 + 240 + 70 + + *Pathfinder* +fontsize=22 +style=wordwrap + + + + Relation + + 450 + 300 + 770 + 30 + + lt=- + 10.0;10.0;750.0;10.0 + + + Text + + 730 + 160 + 130 + 70 + + Movement, collision, etc. +style=wordwrap + + + + Text + + 460 + 160 + 240 + 70 + + *Gamestate* +fontsize=22 +style=wordwrap + + + diff --git a/doc/code/pathfinding/README.md b/doc/code/pathfinding/README.md index f7c5ce6d5b..a7ec70d1bd 100644 --- a/doc/code/pathfinding/README.md +++ b/doc/code/pathfinding/README.md @@ -36,7 +36,7 @@ The most relevant similarity between terrain and pathfinding code is that they u [coordinate systems](/doc/code/coordinate-systems.md#tiletile3). To make the distinction between pathfinding and terrain more clear, we use the term *cells* for tiles in the pathfinder. -![UML pathfinding classes]() +![UML pathfinding classes](/doc/code/images/pathfinder_architecture.svg) The relationship between classes in the pathfinder can be seen above. `Grid` is the top-level structure for storage of movement cost. There may be multiple grids defined, one for each movement type From 50797682e37859263d194dd5de1550d75b415e75 Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 6 May 2024 20:32:50 +0200 Subject: [PATCH 104/112] path: Set start/target in demo with mouse callbacks. --- libopenage/pathfinding/demo/demo_1.cpp | 61 ++++++++++++++++++++++++-- 1 file changed, 57 insertions(+), 4 deletions(-) diff --git a/libopenage/pathfinding/demo/demo_1.cpp b/libopenage/pathfinding/demo/demo_1.cpp index d8da5193e8..7fa71997ba 100644 --- a/libopenage/pathfinding/demo/demo_1.cpp +++ b/libopenage/pathfinding/demo/demo_1.cpp @@ -2,6 +2,8 @@ #include "demo_1.h" +#include + #include "pathfinding/cost_field.h" #include "pathfinding/grid.h" #include "pathfinding/path.h" @@ -80,11 +82,13 @@ void path_demo_1(const util::Path &path) { util::Timer timer; // Create a path request and get the path - // TODO: Make the path request interactive with window callbacks + coord::tile start{2, 26}; + coord::tile target{36, 2}; + PathRequest path_request{ - 0, - coord::tile{2, 26}, - coord::tile{36, 2}, + grid->get_id(), + start, + target, }; timer.start(); Path path_result = pathfinder->get_path(path_request); @@ -98,6 +102,55 @@ void path_demo_1(const util::Path &path) { auto render_manager = std::make_shared(qtapp, window, path, grid); log::log(INFO << "Created render manager for pathfinding demo"); + window->add_mouse_button_callback([&](const QMouseEvent &ev) { + if (ev.type() == QEvent::MouseButtonRelease) { + auto cell_count_x = grid->get_size()[0] * grid->get_sector_size(); + auto cell_count_y = grid->get_size()[1] * grid->get_sector_size(); + auto window_size = window->get_size(); + + auto cell_size_x = window_size[0] / cell_count_x; + auto cell_size_y = window_size[1] / cell_count_y; + + coord::tile_t grid_x = ev.position().x() / cell_size_x; + coord::tile_t grid_y = ev.position().y() / cell_size_y; + + if (ev.button() == Qt::RightButton) { // Set target cell + target = coord::tile{grid_x, grid_y}; + PathRequest new_path_request{ + grid->get_id(), + start, + target, + }; + + timer.start(); + path_result = pathfinder->get_path(new_path_request); + timer.stop(); + + log::log(INFO << "Pathfinding took " << timer.getval() / 1000 << " ps"); + + // Create renderables for the waypoints of the path + render_manager->create_waypoint_tiles(path_result); + } + else if (ev.button() == Qt::LeftButton) { // Set start cell + start = coord::tile{grid_x, grid_y}; + PathRequest new_path_request{ + grid->get_id(), + start, + target, + }; + + timer.start(); + path_result = pathfinder->get_path(new_path_request); + timer.stop(); + + log::log(INFO << "Pathfinding took " << timer.getval() / 1000 << " ps"); + + // Create renderables for the waypoints of the path + render_manager->create_waypoint_tiles(path_result); + } + } + }); + // Create renderables for the waypoints of the path render_manager->create_waypoint_tiles(path_result); From 405e2dd32fa2a9c93aacded7091fff9a01189c80 Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 6 May 2024 20:44:23 +0200 Subject: [PATCH 105/112] path: Draw waypoints in separate render pass. --- libopenage/pathfinding/demo/demo_1.cpp | 29 +++++++++++++++++++++++--- libopenage/pathfinding/demo/demo_1.h | 5 +++++ 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/libopenage/pathfinding/demo/demo_1.cpp b/libopenage/pathfinding/demo/demo_1.cpp index 7fa71997ba..b352f8a91b 100644 --- a/libopenage/pathfinding/demo/demo_1.cpp +++ b/libopenage/pathfinding/demo/demo_1.cpp @@ -179,6 +179,7 @@ void RenderManager1::run() { this->renderer->render(this->background_pass); this->renderer->render(this->field_pass); + this->renderer->render(this->waypoint_pass); this->renderer->render(this->grid_pass); this->renderer->render(this->display_pass); @@ -248,6 +249,18 @@ void RenderManager1::init_passes() { renderer::resources::pixel_format::depth24)); auto grid_fbo = renderer->create_texture_target({grid_texture, depth_texture_3}); + // Make a framebuffer for the waypoint render pass to draw into + auto waypoint_texture = renderer->add_texture( + renderer::resources::Texture2dInfo(size[0], + size[1], + renderer::resources::pixel_format::rgba8)); + auto depth_texture_4 = renderer->add_texture( + renderer::resources::Texture2dInfo(size[0], + size[1], + renderer::resources::pixel_format::depth24)); + auto waypoint_fbo = renderer->create_texture_target({waypoint_texture, depth_texture_4}); + this->waypoint_pass = renderer->add_render_pass({}, waypoint_fbo); + // Create object for the grid auto model = Eigen::Affine3f::Identity(); model.prescale(Eigen::Vector3f{ @@ -289,6 +302,13 @@ void RenderManager1::init_passes() { true, true, }; + auto waypoint_texture_unif = display_shader->new_uniform_input("color_texture", waypoint_texture); + renderer::Renderable waypoint_pass_obj{ + waypoint_texture_unif, + quad, + true, + true, + }; auto grid_texture_unif = display_shader->new_uniform_input("color_texture", grid_texture); renderer::Renderable grid_pass_obj{ grid_texture_unif, @@ -296,8 +316,9 @@ void RenderManager1::init_passes() { true, true, }; + this->display_pass = renderer->add_render_pass( - {bg_pass_obj, field_pass_obj, grid_pass_obj}, + {bg_pass_obj, field_pass_obj, waypoint_pass_obj, grid_pass_obj}, renderer->get_display_target()); } @@ -555,6 +576,8 @@ void RenderManager1::create_waypoint_tiles(const Path &path) { Eigen::Matrix4f id_matrix = Eigen::Matrix4f::Identity(); + this->waypoint_pass->clear_renderables(); + // Draw in-between waypoints for (size_t i = 1; i < path.waypoints.size() - 1; i++) { auto tile = path.waypoints[i]; @@ -596,7 +619,7 @@ void RenderManager1::create_waypoint_tiles(const Path &path) { true, true, }; - this->field_pass->add_renderables({tile_obj}); + this->waypoint_pass->add_renderables({tile_obj}); } // Draw start and end waypoints with different colors @@ -679,7 +702,7 @@ void RenderManager1::create_waypoint_tiles(const Path &path) { true, }; - this->field_pass->add_renderables({start_tile_obj, end_tile_obj}); + this->waypoint_pass->add_renderables({start_tile_obj, end_tile_obj}); } } // namespace openage::path::tests diff --git a/libopenage/pathfinding/demo/demo_1.h b/libopenage/pathfinding/demo/demo_1.h index eafcc3d45f..db70efe084 100644 --- a/libopenage/pathfinding/demo/demo_1.h +++ b/libopenage/pathfinding/demo/demo_1.h @@ -169,6 +169,11 @@ class RenderManager1 { */ std::shared_ptr grid_pass; + /** + * Waypoint pass: Renders the path and its waypoints. + */ + std::shared_ptr waypoint_pass; + /** * Display pass: Draws the results of previous passes to the screen. */ From e0b63a270bf6548e6112a4c7ed7db0cba696de8b Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 6 May 2024 20:49:33 +0200 Subject: [PATCH 106/112] path: Fix check for flow field count. --- libopenage/pathfinding/pathfinder.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libopenage/pathfinding/pathfinder.cpp b/libopenage/pathfinding/pathfinder.cpp index 3f3c7fb24f..1b6cfe91e2 100644 --- a/libopenage/pathfinding/pathfinder.cpp +++ b/libopenage/pathfinding/pathfinder.cpp @@ -194,7 +194,7 @@ const std::vector> Pathfinder::portal_a_star(const PathR const std::vector Pathfinder::get_waypoints(const std::vector>> &flow_fields, const PathRequest &request) const { - ENSURE(flow_fields.size() > 1, "At least 1 flow field is required for finding waypoints."); + ENSURE(flow_fields.size() > 0, "At least 1 flow field is required for finding waypoints."); std::vector waypoints; From 8b1d427e847c49d353e07317697cfdb679bde433 Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 6 May 2024 21:14:07 +0200 Subject: [PATCH 107/112] path: Use distance between portal centers for A star distance cost. --- libopenage/pathfinding/pathfinder.cpp | 15 ++++++++++++++- libopenage/pathfinding/pathfinder.h | 10 ++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/libopenage/pathfinding/pathfinder.cpp b/libopenage/pathfinding/pathfinder.cpp index 1b6cfe91e2..b5322f140c 100644 --- a/libopenage/pathfinding/pathfinder.cpp +++ b/libopenage/pathfinding/pathfinder.cpp @@ -104,7 +104,7 @@ const std::vector> Pathfinder::portal_a_star(const PathR // Cost to travel from one portal to another // TODO: Determine this cost for each portal - const int distance_cost = 1; + // const int distance_cost = 1; // start nodes: all portals in the start sector for (auto &portal : start_sector->get_portals()) { @@ -156,6 +156,11 @@ const std::vector> Pathfinder::portal_a_star(const PathR continue; } + // Get distance cost from current node to exit + 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; @@ -328,6 +333,14 @@ int Pathfinder::heuristic_cost(const coord::tile &portal_pos, return delta.length(); } +int Pathfinder::distance_cost(const coord::tile &portal1_pos, + const coord::tile &portal2_pos) { + auto delta = portal2_pos.to_phys2() - portal1_pos.to_phys2(); + + return delta.length(); +} + + PortalNode::PortalNode(const std::shared_ptr &portal, sector_id_t entry_sector, const node_t &prev_portal) : diff --git a/libopenage/pathfinding/pathfinder.h b/libopenage/pathfinding/pathfinder.h index e6e43a7abf..a4df0350d8 100644 --- a/libopenage/pathfinding/pathfinder.h +++ b/libopenage/pathfinding/pathfinder.h @@ -92,6 +92,16 @@ 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. + * @param portal2_pos Center of the second portal. + * + * @return Distance cost between the portal centers. + */ + static int distance_cost(const coord::tile &portal1_pos, const coord::tile &portal2_pos); + /** * Grids managed by this pathfinder. * From 9ebe429c391c23afd005a1a87eecd23ebee1e97e Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 6 May 2024 21:19:58 +0200 Subject: [PATCH 108/112] path: Fix setting start/target position. --- libopenage/pathfinding/demo/demo_1.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libopenage/pathfinding/demo/demo_1.cpp b/libopenage/pathfinding/demo/demo_1.cpp index b352f8a91b..f3bd42f625 100644 --- a/libopenage/pathfinding/demo/demo_1.cpp +++ b/libopenage/pathfinding/demo/demo_1.cpp @@ -108,8 +108,8 @@ void path_demo_1(const util::Path &path) { auto cell_count_y = grid->get_size()[1] * grid->get_sector_size(); auto window_size = window->get_size(); - auto cell_size_x = window_size[0] / cell_count_x; - auto cell_size_y = window_size[1] / cell_count_y; + double cell_size_x = static_cast(window_size[0]) / cell_count_x; + double cell_size_y = static_cast(window_size[1]) / cell_count_y; coord::tile_t grid_x = ev.position().x() / cell_size_x; coord::tile_t grid_y = ev.position().y() / cell_size_y; From c6b7b28653ceba2dab2b2c7ac685a121adcda6bd Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 6 May 2024 21:53:24 +0200 Subject: [PATCH 109/112] path: Use window_settings type to set window size, --- libopenage/pathfinding/demo/demo_0.cpp | 7 ++++++- libopenage/pathfinding/demo/demo_1.cpp | 6 +++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/libopenage/pathfinding/demo/demo_0.cpp b/libopenage/pathfinding/demo/demo_0.cpp index e87b44d1f0..f8970459d4 100644 --- a/libopenage/pathfinding/demo/demo_0.cpp +++ b/libopenage/pathfinding/demo/demo_0.cpp @@ -59,7 +59,12 @@ void path_demo_0(const util::Path &path) { // Render the grid and the pathfinding results auto qtapp = std::make_shared(); - auto window = std::make_shared("openage pathfinding test", 1440, 720); + + // Create a window for rendering + renderer::window_settings settings; + settings.width = 1440; + settings.height = 720; + auto window = std::make_shared("openage pathfinding test", settings); auto render_manager = std::make_shared(qtapp, window, path); log::log(INFO << "Created render manager for pathfinding demo"); diff --git a/libopenage/pathfinding/demo/demo_1.cpp b/libopenage/pathfinding/demo/demo_1.cpp index f3bd42f625..be34e95af2 100644 --- a/libopenage/pathfinding/demo/demo_1.cpp +++ b/libopenage/pathfinding/demo/demo_1.cpp @@ -98,7 +98,11 @@ void path_demo_1(const util::Path &path) { // Create a renderer to display the grid and path auto qtapp = std::make_shared(); - auto window = std::make_shared("openage pathfinding test", 1024, 768); + + renderer::window_settings settings; + settings.width = 1024; + settings.height = 768; + auto window = std::make_shared("openage pathfinding test", settings); auto render_manager = std::make_shared(qtapp, window, path, grid); log::log(INFO << "Created render manager for pathfinding demo"); From 9a5ac8d925b48bf282d032ef779189ae2551d659 Mon Sep 17 00:00:00 2001 From: heinezen Date: Tue, 7 May 2024 00:24:21 +0200 Subject: [PATCH 110/112] path: Avoid narrowing conversion to coord types. --- libopenage/pathfinding/demo/demo_0.cpp | 26 ++++++++++---------- libopenage/pathfinding/demo/demo_1.cpp | 6 ++--- libopenage/pathfinding/grid.cpp | 7 +++--- libopenage/pathfinding/integration_field.cpp | 2 +- libopenage/pathfinding/pathfinder.cpp | 18 +++++++------- libopenage/pathfinding/sector.cpp | 16 ++++++------ 6 files changed, 37 insertions(+), 38 deletions(-) diff --git a/libopenage/pathfinding/demo/demo_0.cpp b/libopenage/pathfinding/demo/demo_0.cpp index f8970459d4..9918d22bdc 100644 --- a/libopenage/pathfinding/demo/demo_0.cpp +++ b/libopenage/pathfinding/demo/demo_0.cpp @@ -272,7 +272,7 @@ void RenderManager0::show_vectors(const std::shared_ptr &field) this->vector_pass->clear_renderables(); for (size_t y = 0; y < field->get_size(); ++y) { for (size_t x = 0; x < field->get_size(); ++x) { - auto cell = field->get_cell(coord::tile{x, y}); + auto cell = field->get_cell(coord::tile(x, y)); if (cell & FLOW_PATHABLE_MASK and not(cell & FLOW_LOS_MASK)) { Eigen::Affine3f arrow_model = Eigen::Affine3f::Identity(); arrow_model.translate(Eigen::Vector3f(y + 0.5, 0, -1.0f * x - 0.5)); @@ -563,19 +563,19 @@ renderer::resources::MeshData RenderManager0::get_cost_field_mesh(const std::sha // for each vertex, compare the surrounding tiles std::vector surround{}; if (j - 1 >= 0 and i - 1 >= 0) { - auto cost = field->get_cost(coord::tile{(i - 1) / resolution, (j - 1) / resolution}); + auto cost = field->get_cost(coord::tile((i - 1) / resolution, (j - 1) / resolution)); surround.push_back(cost); } if (j < static_cast(field->get_size()) and i - 1 >= 0) { - auto cost = field->get_cost(coord::tile{(i - 1) / resolution, j / resolution}); + auto cost = field->get_cost(coord::tile((i - 1) / resolution, j / resolution)); surround.push_back(cost); } if (j < static_cast(field->get_size()) and i < static_cast(field->get_size())) { - auto cost = field->get_cost(coord::tile{i / resolution, j / resolution}); + auto cost = field->get_cost(coord::tile(i / resolution, j / resolution)); surround.push_back(cost); } if (j - 1 >= 0 and i < static_cast(field->get_size())) { - auto cost = field->get_cost(coord::tile{i / resolution, (j - 1) / resolution}); + auto cost = field->get_cost(coord::tile(i / resolution, (j - 1) / resolution)); surround.push_back(cost); } // use the cost of the most expensive surrounding tile @@ -649,19 +649,19 @@ renderer::resources::MeshData RenderManager0::get_integration_field_mesh(const s // for each vertex, compare the surrounding tiles std::vector surround{}; if (j - 1 >= 0 and i - 1 >= 0) { - auto cost = field->get_cell(coord::tile{(i - 1) / resolution, (j - 1) / resolution}).cost; + auto cost = field->get_cell(coord::tile((i - 1) / resolution, (j - 1) / resolution)).cost; surround.push_back(cost); } if (j < static_cast(field->get_size()) and i - 1 >= 0) { - auto cost = field->get_cell(coord::tile{(i - 1) / resolution, j / resolution}).cost; + auto cost = field->get_cell(coord::tile((i - 1) / resolution, j / resolution)).cost; surround.push_back(cost); } if (j < static_cast(field->get_size()) and i < static_cast(field->get_size())) { - auto cost = field->get_cell(coord::tile{i / resolution, j / resolution}).cost; + auto cost = field->get_cell(coord::tile(i / resolution, j / resolution)).cost; surround.push_back(cost); } if (j - 1 >= 0 and i < static_cast(field->get_size())) { - auto cost = field->get_cell(coord::tile{i / resolution, (j - 1) / resolution}).cost; + auto cost = field->get_cell(coord::tile(i / resolution, (j - 1) / resolution)).cost; surround.push_back(cost); } // use the cost of the most expensive surrounding tile @@ -735,19 +735,19 @@ renderer::resources::MeshData RenderManager0::get_flow_field_mesh(const std::sha // for each vertex, compare the surrounding tiles std::vector surround{}; if (j - 1 >= 0 and i - 1 >= 0) { - auto cost = field->get_cell(coord::tile{(i - 1) / resolution, (j - 1) / resolution}); + auto cost = field->get_cell(coord::tile((i - 1) / resolution, (j - 1) / resolution)); surround.push_back(cost); } if (j < static_cast(field->get_size()) and i - 1 >= 0) { - auto cost = field->get_cell(coord::tile{(i - 1) / resolution, j / resolution}); + auto cost = field->get_cell(coord::tile((i - 1) / resolution, j / resolution)); surround.push_back(cost); } if (j < static_cast(field->get_size()) and i < static_cast(field->get_size())) { - auto cost = field->get_cell(coord::tile{i / resolution, j / resolution}); + auto cost = field->get_cell(coord::tile(i / resolution, j / resolution)); surround.push_back(cost); } if (j - 1 >= 0 and i < static_cast(field->get_size())) { - auto cost = field->get_cell(coord::tile{i / resolution, (j - 1) / resolution}); + auto cost = field->get_cell(coord::tile(i / resolution, (j - 1) / resolution)); surround.push_back(cost); } // use the cost of the most expensive surrounding tile diff --git a/libopenage/pathfinding/demo/demo_1.cpp b/libopenage/pathfinding/demo/demo_1.cpp index be34e95af2..1a4ea42ac6 100644 --- a/libopenage/pathfinding/demo/demo_1.cpp +++ b/libopenage/pathfinding/demo/demo_1.cpp @@ -445,7 +445,7 @@ void RenderManager1::create_impassible_tiles(const std::shared_ptr & auto cost_field = sector->get_cost_field(); for (size_t y = 0; y < sector_size; y++) { for (size_t x = 0; x < sector_size; x++) { - auto cost = cost_field->get_cost(coord::tile{x, y}); + auto cost = cost_field->get_cost(coord::tile(x, y)); if (cost == COST_IMPASSABLE) { std::array tile_data{ -1.0f + x * tile_offset_width + sector_size * sector_x * tile_offset_width, @@ -514,13 +514,13 @@ void RenderManager1::create_portal_tiles(const std::shared_ptr &grid if (direction == PortalDirection::NORTH_SOUTH) { auto y = start.se; for (auto x = start.ne; x <= end.ne; ++x) { - tiles.push_back(coord::tile{x, y}); + tiles.push_back(coord::tile(x, y)); } } else { auto x = start.ne; for (auto y = start.se; y <= end.se; ++y) { - tiles.push_back(coord::tile{x, y}); + tiles.push_back(coord::tile(x, y)); } } diff --git a/libopenage/pathfinding/grid.cpp b/libopenage/pathfinding/grid.cpp index 4100492c16..62a2d3156f 100644 --- a/libopenage/pathfinding/grid.cpp +++ b/libopenage/pathfinding/grid.cpp @@ -20,10 +20,9 @@ Grid::Grid(grid_id_t id, for (size_t y = 0; y < size[1]; y++) { for (size_t x = 0; x < size[0]; x++) { this->sectors.push_back( - std::make_shared( - x + y * this->size[0], - coord::chunk{x, y}, - sector_size)); + std::make_shared(x + y * this->size[0], + coord::chunk(x, y), + sector_size)); } } } diff --git a/libopenage/pathfinding/integration_field.cpp b/libopenage/pathfinding/integration_field.cpp index 201823eec4..bc2745879f 100644 --- a/libopenage/pathfinding/integration_field.cpp +++ b/libopenage/pathfinding/integration_field.cpp @@ -98,7 +98,7 @@ std::vector IntegrationField::integrate_los(const std::shared_ptrget_los_corners(cost_field, target, coord::tile{x, y}); + auto corners = this->get_los_corners(cost_field, target, coord::tile(x, y)); for (auto &corner : corners) { // draw a line from the corner to the edge of the field // to get the cells blocked by the corner diff --git a/libopenage/pathfinding/pathfinder.cpp b/libopenage/pathfinding/pathfinder.cpp index b5322f140c..7e09278f7c 100644 --- a/libopenage/pathfinding/pathfinder.cpp +++ b/libopenage/pathfinding/pathfinder.cpp @@ -112,7 +112,7 @@ const std::vector> Pathfinder::portal_a_star(const PathR auto sector_pos = grid->get_sector(portal->get_exit_sector(start_sector->get_id()))->get_position(); auto portal_pos = portal->get_exit_center(start_sector->get_id()); - auto portal_abs_pos = portal_pos + coord::tile_delta{sector_pos.ne * sector_size, sector_pos.se * sector_size}; + auto portal_abs_pos = portal_pos + coord::tile_delta(sector_pos.ne * sector_size, sector_pos.se * sector_size); auto heuristic_cost = Pathfinder::heuristic_cost(portal_abs_pos, request.target); portal_node->current_cost = 0; @@ -226,21 +226,21 @@ const std::vector Pathfinder::get_waypoints(const std::vectorget_position(); - auto cell_pos = coord::tile{sector_pos.ne * sector_size, - sector_pos.se * sector_size} - + coord::tile_delta{current_x, current_y}; + auto cell_pos = coord::tile(sector_pos.ne * sector_size, + sector_pos.se * sector_size) + + coord::tile_delta(current_x, current_y); waypoints.push_back(cell_pos); break; } // check if we need to change direction - auto cell_direction = flow_field->get_dir(coord::tile{current_x, current_y}); + auto cell_direction = flow_field->get_dir(coord::tile(current_x, current_y)); if (cell_direction != current_direction) { // add the current cell as a waypoint auto sector_pos = sector->get_position(); - auto cell_pos = coord::tile{sector_pos.ne * sector_size, - sector_pos.se * sector_size} - + coord::tile_delta{current_x, current_y}; + auto cell_pos = coord::tile(sector_pos.ne * sector_size, + sector_pos.se * sector_size) + + coord::tile_delta(current_x, current_y); waypoints.push_back(cell_pos); current_direction = cell_direction; } @@ -317,7 +317,7 @@ const std::vector Pathfinder::get_waypoints(const std::vectorget_sector(flow_fields.back().first)->get_position(); - auto target_pos = coord::tile{sector_pos.ne * sector_size, sector_pos.se * sector_size} + auto target_pos = coord::tile(sector_pos.ne * sector_size, sector_pos.se * sector_size) + coord::tile_delta{target_x, target_y}; waypoints.push_back(target_pos); diff --git a/libopenage/pathfinding/sector.cpp b/libopenage/pathfinding/sector.cpp index 6b56114e47..cf6ffaa755 100644 --- a/libopenage/pathfinding/sector.cpp +++ b/libopenage/pathfinding/sector.cpp @@ -65,13 +65,13 @@ std::vector> Sector::find_portals(const std::shared_ptr< auto coord_other = coord::tile{0, 0}; if (direction == PortalDirection::NORTH_SOUTH) { // right edge; top to bottom - coord_this = coord::tile{i, this->cost_field->get_size() - 1}; - coord_other = coord::tile{i, 0}; + coord_this = coord::tile(i, this->cost_field->get_size() - 1); + coord_other = coord::tile(i, 0); } else if (direction == PortalDirection::EAST_WEST) { // bottom edge; east to west - coord_this = coord::tile{this->cost_field->get_size() - 1, i}; - coord_other = coord::tile{0, i}; + coord_this = coord::tile(this->cost_field->get_size() - 1, i); + coord_other = coord::tile(0, i); } if (this->cost_field->get_cost(coord_this) != COST_IMPASSABLE @@ -98,13 +98,13 @@ std::vector> Sector::find_portals(const std::shared_ptr< auto coord_end = coord::tile{0, 0}; if (direction == PortalDirection::NORTH_SOUTH) { // right edge; top to bottom - coord_start = coord::tile{start, this->cost_field->get_size() - 1}; - coord_end = coord::tile{end, this->cost_field->get_size() - 1}; + coord_start = coord::tile(start, this->cost_field->get_size() - 1); + coord_end = coord::tile(end, this->cost_field->get_size() - 1); } else if (direction == PortalDirection::EAST_WEST) { // bottom edge; east to west - coord_start = coord::tile{this->cost_field->get_size() - 1, start}; - coord_end = coord::tile{this->cost_field->get_size() - 1, end}; + coord_start = coord::tile(this->cost_field->get_size() - 1, start); + coord_end = coord::tile(this->cost_field->get_size() - 1, end); } result.push_back( From cfebd22862a1374d3bbac6428e60b491a8b1881b Mon Sep 17 00:00:00 2001 From: heinezen Date: Tue, 7 May 2024 23:04:01 +0200 Subject: [PATCH 111/112] path: Reset timer when path is recalculated. --- libopenage/pathfinding/demo/demo_1.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libopenage/pathfinding/demo/demo_1.cpp b/libopenage/pathfinding/demo/demo_1.cpp index 1a4ea42ac6..5a90f97218 100644 --- a/libopenage/pathfinding/demo/demo_1.cpp +++ b/libopenage/pathfinding/demo/demo_1.cpp @@ -126,6 +126,7 @@ void path_demo_1(const util::Path &path) { target, }; + timer.reset(); timer.start(); path_result = pathfinder->get_path(new_path_request); timer.stop(); @@ -143,6 +144,7 @@ void path_demo_1(const util::Path &path) { target, }; + timer.reset(); timer.start(); path_result = pathfinder->get_path(new_path_request); timer.stop(); From 12cd944d805abe483b3edbae724de24f3a2e8e42 Mon Sep 17 00:00:00 2001 From: heinezen Date: Fri, 24 May 2024 03:27:07 +0200 Subject: [PATCH 112/112] path: Fix includes after rebase. --- libopenage/pathfinding/demo/demo_0.cpp | 4 +++- libopenage/pathfinding/demo/demo_1.cpp | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/libopenage/pathfinding/demo/demo_0.cpp b/libopenage/pathfinding/demo/demo_0.cpp index 9918d22bdc..da7ae17492 100644 --- a/libopenage/pathfinding/demo/demo_0.cpp +++ b/libopenage/pathfinding/demo/demo_0.cpp @@ -12,6 +12,8 @@ #include "renderer/camera/camera.h" #include "renderer/gui/integration/public/gui_application_with_logger.h" #include "renderer/opengl/window.h" +#include "renderer/render_pass.h" +#include "renderer/renderable.h" #include "renderer/renderer.h" #include "renderer/resources/mesh_data.h" #include "renderer/resources/shader_source.h" @@ -298,7 +300,7 @@ void RenderManager0::show_vectors(const std::shared_ptr &field) true, true, }; - this->vector_pass->add_renderables(arrow_renderable); + this->vector_pass->add_renderables(std::move(arrow_renderable)); } } } diff --git a/libopenage/pathfinding/demo/demo_1.cpp b/libopenage/pathfinding/demo/demo_1.cpp index 5a90f97218..21aaa2c9d8 100644 --- a/libopenage/pathfinding/demo/demo_1.cpp +++ b/libopenage/pathfinding/demo/demo_1.cpp @@ -14,6 +14,8 @@ #include "renderer/gui/integration/public/gui_application_with_logger.h" #include "renderer/opengl/window.h" +#include "renderer/render_pass.h" +#include "renderer/renderable.h" #include "renderer/resources/shader_source.h" #include "renderer/resources/texture_info.h" #include "renderer/shader_program.h"