3#define TESS_PATH_PATH_H_INCLUDED 1
5#include <tess/core/shape.h>
6#include <tess/diagnostics/diagnostics.h>
7#include <tess/path/node_index_space.h>
8#include <tess/path/path_view.h>
9#include <tess/topology/movement_class.h>
26enum class PathStatus : std::uint8_t {
37static_assert(
sizeof(PathStatus) ==
sizeof(std::uint8_t));
42enum class MissingChunkPolicy : std::uint8_t {
64 PathStatus status = PathStatus::NoPath;
65 std::uint32_t cost = 0;
66 std::size_t expanded_nodes = 0;
67 std::size_t reached_nodes = 0;
73 PathStatus status = PathStatus::NoPath;
74 std::size_t expanded_nodes = 0;
75 std::size_t reached_nodes = 0;
81 std::size_t requests = 0;
82 std::size_t unique_goals = 0;
83 std::size_t field_builds = 0;
84 std::size_t astar_fallbacks = 0;
85 std::size_t path_nodes = 0;
107template <
typename World,
typename Class>
108auto weighted_distance_field_path_core(
const World& world,
Coord3 start,
118template <
typename World,
typename Class, std::u
int32_t MaxCost>
119auto build_bounded_weighted_distance_field_core(
121 MissingChunkPolicy policy, std::span<const std::uint64_t> settle_targets)
131template <
typename World,
typename Tag>
132auto astar_path(
const World& world, PathRequest request, PathScratch& scratch,
133 MissingChunkPolicy policy = MissingChunkPolicy::TreatAsBlocked)
143template <
typename World,
typename Class>
144auto weighted_astar_path(
145 const World& world, PathRequest request, PathScratch& scratch,
146 MissingChunkPolicy policy = MissingChunkPolicy::TreatAsBlocked)
149template <
typename World,
typename PassableTag,
typename CostTag>
151auto weighted_astar_path(
152 const World& world, PathRequest request, PathScratch& scratch,
153 MissingChunkPolicy policy = MissingChunkPolicy::TreatAsBlocked)
157template <
typename World,
typename Tag>
158auto build_distance_field_product(
const World& world,
const GoalSet& goals,
159 DistanceFieldScratch& scratch,
160 DistanceFieldProduct& product)
161 -> DistanceFieldResult;
164template <
typename World,
typename Tag>
165auto distance_field_product_path(
const World& world, Coord3 start,
166 const DistanceFieldProduct& product,
167 DistanceFieldScratch& scratch) -> PathResult;
170template <
typename World,
typename Tag>
171auto nearest_target(
const World& world, Coord3 start,
172 const DistanceFieldProduct& product,
173 DistanceFieldScratch& scratch) -> NearestTargetResult;
179template <
typename WorldType,
typename Tag>
180auto build_distance_field(
181 const WorldType& world, Coord3 goal, DistanceFieldScratch& scratch,
182 MissingChunkPolicy policy = MissingChunkPolicy::TreatAsBlocked)
183 -> DistanceFieldResult;
189template <
typename WorldType,
typename Class>
190auto build_weighted_distance_field(
191 const WorldType& world, Coord3 goal, DistanceFieldScratch& scratch,
192 MissingChunkPolicy policy = MissingChunkPolicy::TreatAsBlocked)
193 -> DistanceFieldResult;
195template <
typename World,
typename PassableTag,
typename CostTag>
197auto build_weighted_distance_field(
198 const World& world, Coord3 goal, DistanceFieldScratch& scratch,
199 MissingChunkPolicy policy = MissingChunkPolicy::TreatAsBlocked)
200 -> DistanceFieldResult;
203template <
typename World,
typename Class>
204auto build_weighted_distance_field_in_box(
205 const World& world, Coord3 goal, Box3 domain, DistanceFieldScratch& scratch,
206 MissingChunkPolicy policy = MissingChunkPolicy::TreatAsBlocked)
207 -> DistanceFieldResult;
209template <
typename World,
typename PassableTag,
typename CostTag>
211auto build_weighted_distance_field_in_box(
212 const World& world, Coord3 goal, Box3 domain, DistanceFieldScratch& scratch,
213 MissingChunkPolicy policy = MissingChunkPolicy::TreatAsBlocked)
214 -> DistanceFieldResult;
217template <
typename World,
typename Class, std::u
int32_t MaxCost>
218auto build_bounded_weighted_distance_field(
219 const World& world, Coord3 goal, DistanceFieldScratch& scratch,
220 MissingChunkPolicy policy = MissingChunkPolicy::TreatAsBlocked)
221 -> DistanceFieldResult;
223template <
typename World,
typename PassableTag,
typename CostTag,
224 std::uint32_t MaxCost>
226auto build_bounded_weighted_distance_field(
227 const World& world, Coord3 goal, DistanceFieldScratch& scratch,
228 MissingChunkPolicy policy = MissingChunkPolicy::TreatAsBlocked)
229 -> DistanceFieldResult;
238 std::uint32_t version = 0;
241 void reserve(std::size_t count) { chunks_.reserve(count); }
243 void clear() noexcept { chunks_.clear(); }
245 template <
typename World>
246 void capture_all(
const World& world) {
248 chunks_.reserve(
static_cast<std::size_t
>(World::chunk_count));
251 for (std::uint64_t i = 0; i < World::chunk_count; ++i) {
253 ChunkVersionDependency{ChunkKey{i}, world.meta(ChunkKey{i}).version});
259 template <
typename World>
260 void add_chunk_unique(
const World& world, ChunkKey key) {
264 template <
typename World>
265 void add_chunk(
const World& world, ChunkKey key) {
266 const auto version = world.meta(key).version;
269 if (!chunks_.empty() && chunks_.back().key == key) {
270 chunks_.back().version = version;
273 for (
auto& chunk : chunks_) {
274 if (chunk.key == key) {
275 chunk.version = version;
282 template <
typename World>
283 [[nodiscard]]
auto is_valid(
const World& world)
const noexcept ->
bool {
284 for (
const auto chunk : chunks_) {
285 if (world.meta(chunk.key).version != chunk.version) {
292 [[nodiscard]]
auto size() const noexcept -> std::
size_t {
293 return chunks_.size();
296 [[nodiscard]]
auto empty() const noexcept ->
bool {
return chunks_.empty(); }
298 [[nodiscard]]
auto chunks() const noexcept
304 std::vector<ChunkVersionDependency> chunks_;
319template <
typename Shape,
typename World>
320void capture_failure_dependencies(
const World& world, PathRequest request,
322 ChunkVersionDependencies& dependencies) {
323 if (status == PathStatus::NoPath) {
324 dependencies.capture_all(world);
327 if (contains<Shape>(request.start)) {
328 dependencies.add_chunk(world,
329 chunk_key<Shape>(tile_key<Shape>(request.start)));
331 if (contains<Shape>(request.goal)) {
332 dependencies.add_chunk(world,
333 chunk_key<Shape>(tile_key<Shape>(request.goal)));
344 void reserve_path_nodes(std::size_t node_count) { path_.reserve(node_count); }
349 void reserve_dependencies(std::size_t count) { dependencies_.reserve(count); }
351 void clear()
noexcept {
353 status_ = PathStatus::NoPath;
358 dependencies_.clear();
365 template <
typename World>
366 [[nodiscard]]
auto is_valid(
const World& world)
const noexcept ->
bool {
367 return !dependencies_.empty() && dependencies_.is_valid(world);
370 [[nodiscard]]
auto request()
const noexcept ->
PathRequest {
374 [[nodiscard]]
auto dependencies()
const noexcept
375 -> std::span<const ChunkVersionDependencies::ChunkVersionDependency> {
376 return dependencies_.chunks();
380 template <
typename World,
typename PassableTag,
typename CostTag>
387 template <
typename World>
393 PathStatus status_ = PathStatus::NoPath;
394 std::uint32_t cost_ = 0;
395 std::size_t expanded_nodes_ = 0;
396 std::size_t reached_nodes_ = 0;
397 std::vector<Coord3> path_;
410 void reserve_waypoints(std::size_t count) {
411 waypoints_.reserve(count);
412 candidate_waypoints_.reserve(count);
413 best_waypoints_.reserve(count);
416 void reserve_path_nodes(std::size_t node_count) {
417 path_.reserve(node_count);
418 segment_.reserve(node_count);
424 void reserve_dependencies(std::size_t count) { dependencies_.reserve(count); }
426 void clear()
noexcept {
428 status_ = PathStatus::NoPath;
432 route_candidates_ = 0;
433 portal_scan_tiles_ = 0;
435 candidate_waypoints_.clear();
436 best_waypoints_.clear();
439 dependencies_.clear();
444 template <
typename World>
445 [[nodiscard]]
auto is_valid(
const World& world)
const noexcept ->
bool {
446 return !dependencies_.empty() && dependencies_.is_valid(world);
449 [[nodiscard]]
auto request()
const noexcept ->
PathRequest {
453 [[nodiscard]]
auto waypoints()
const noexcept -> std::span<const Coord3> {
457 [[nodiscard]]
auto dependencies()
const noexcept
458 -> std::span<const ChunkVersionDependencies::ChunkVersionDependency> {
459 return dependencies_.chunks();
462 [[nodiscard]]
auto route_candidates()
const noexcept -> std::size_t {
463 return route_candidates_;
466 [[nodiscard]]
auto portal_scan_tiles()
const noexcept -> std::size_t {
467 return portal_scan_tiles_;
471 template <
typename World,
typename PassableTag,
typename CostTag>
474 std::span<const Coord3> waypoints,
PathScratch& scratch,
477 template <
typename World,
typename PassableTag,
typename CostTag>
480 std::span<const Coord3> waypoints,
PathScratch& scratch,
484 template <
typename World,
typename PassableTag,
typename CostTag>
489 template <
typename World>
498 [[nodiscard]]
auto stash_if_owned(std::span<const Coord3> input,
499 std::vector<Coord3>& stash)
const
500 -> std::span<const Coord3> {
501 const auto owned = [&](
const std::vector<Coord3>& storage) {
502 const auto* begin = storage.data();
503 const auto* end = begin + storage.size();
504 return !input.empty() &&
505 !std::less<const Coord3*>{}(input.data(), begin) &&
506 std::less<const Coord3*>{}(input.data(), end);
508 if (owned(waypoints_) || owned(path_) || owned(segment_) ||
509 owned(candidate_waypoints_) || owned(best_waypoints_)) {
510 stash.assign(input.begin(), input.end());
511 return std::span<const Coord3>{stash};
517 PathStatus status_ = PathStatus::NoPath;
518 std::uint32_t cost_ = 0;
519 std::size_t expanded_nodes_ = 0;
520 std::size_t reached_nodes_ = 0;
521 std::size_t route_candidates_ = 0;
522 std::size_t portal_scan_tiles_ = 0;
523 std::vector<Coord3> waypoints_;
524 std::vector<Coord3> candidate_waypoints_;
525 std::vector<Coord3> best_waypoints_;
526 std::vector<Coord3> path_;
527 std::vector<Coord3> segment_;
538 std::uint64_t index = 0;
543 void reserve_nodes(std::size_t node_count) {
544 open_.reserve(node_count);
545 open_next_.reserve(node_count);
546 generation_.reserve(node_count);
547 state_.reserve(node_count);
548 g_.reserve(node_count);
549 parent_.reserve(node_count);
550 path_.reserve(node_count);
553 void clear() noexcept {
561 [[nodiscard]]
auto capacity_nodes() const noexcept -> std::
size_t {
562 return state_.capacity();
566 template <
typename World,
typename Tag>
571 template <
typename World,
typename Class>
576 template <
typename World,
typename PassableTag,
typename CostTag>
581 template <
typename World,
typename Tag>
586 void advance_epoch() noexcept {
589 std::fill(generation_.begin(), generation_.end(), 0);
594 [[nodiscard]]
auto is_current(std::size_t offset)
const noexcept ->
bool {
595 return generation_[offset] == epoch_;
598 [[nodiscard]]
auto state_at(std::size_t offset,
599 std::uint8_t unseen)
const noexcept
601 return is_current(offset) ? state_[offset] : unseen;
604 [[nodiscard]]
auto g_at(std::size_t offset,
605 std::uint32_t infinite_cost)
const noexcept
607 return is_current(offset) ? g_[offset] : infinite_cost;
613 void touch_node(std::size_t offset) {
614 generation_[offset] = epoch_;
618 std::vector<OpenNode> open_;
619 std::vector<OpenNode> open_next_;
625 std::vector<std::uint32_t> generation_;
626 std::uint32_t epoch_ = 1;
627 std::vector<std::uint8_t> state_;
628 std::vector<std::uint32_t> g_;
629 std::vector<std::uint64_t> parent_;
634 std::size_t touched_count_ = 0;
635 std::vector<Coord3> path_;
644 void reserve_nodes(std::size_t node_count) {
645 frontier_.reserve(node_count);
646 weighted_frontier_.reserve(node_count);
647 weighted_bucket_capacity_ = node_count / 8u + 1u;
648 for (
auto& bucket : weighted_buckets_) {
649 bucket.reserve(weighted_bucket_capacity_);
651 generation_.reserve(node_count);
652 distance_.reserve(node_count);
653 target_generation_.reserve(node_count);
654 touched_.reserve(node_count);
655 path_.reserve(node_count);
658 [[nodiscard]]
auto capacity_nodes()
const noexcept -> std::size_t {
659 return distance_.capacity();
663 template <
typename WorldType,
typename Tag>
666 MissingChunkPolicy policy)
669 template <
typename World,
typename Tag>
675 template <
typename WorldType,
typename Class>
678 MissingChunkPolicy policy)
681 template <
typename World,
typename Class>
687 template <
typename World,
typename Class, std::u
int32_t MaxCost>
688 friend auto detail::build_bounded_weighted_distance_field_core(
690 MissingChunkPolicy policy, std::span<const std::uint64_t> settle_targets)
695 template <
typename World,
typename Class>
696 friend auto detail::weighted_distance_field_path_core(
702 template <
typename World,
typename Class, std::u
int32_t MaxCost>
703 friend auto weighted_path_batch(
const World& world,
704 std::span<const PathRequest> requests,
706 -> std::span<const PathResult>;
708 template <
typename World,
typename Tag>
715 template <
typename World,
typename Tag>
721 template <
typename World,
typename Tag>
727 void clear_build()
noexcept {
730 weighted_frontier_.clear();
731 for (
auto& bucket : weighted_buckets_) {
739 void clear_path()
noexcept { path_.clear(); }
741 void advance_epoch()
noexcept {
744 std::fill(generation_.begin(), generation_.end(), 0);
745 std::fill(target_generation_.begin(), target_generation_.end(), 0);
750 [[nodiscard]]
auto is_current(std::size_t offset)
const noexcept ->
bool {
751 return generation_[offset] == epoch_;
754 [[nodiscard]]
auto distance_at(std::size_t offset,
755 std::uint32_t infinite_distance)
const noexcept
757 return is_current(offset) ? distance_[offset] : infinite_distance;
760 void touch_node(std::size_t offset, std::uint64_t index) {
761 generation_[offset] = epoch_;
762 touched_.push_back(index);
768 void touch_node(std::uint64_t index) {
769 touch_node(
static_cast<std::size_t
>(index), index);
775 void mark_settle_target(std::size_t offset) {
776 target_generation_[offset] = epoch_;
779 [[nodiscard]]
auto is_settle_target(std::size_t offset)
const noexcept
781 return target_generation_[offset] == epoch_;
784 std::vector<std::uint64_t> frontier_;
785 std::vector<PathScratch::OpenNode> weighted_frontier_;
786 std::vector<std::vector<std::uint64_t>> weighted_buckets_;
787 std::size_t weighted_bucket_capacity_ = 0;
788 std::vector<std::uint32_t> generation_;
789 std::uint32_t epoch_ = 1;
790 std::vector<std::uint32_t> distance_;
791 std::vector<std::uint32_t> target_generation_;
792 std::vector<std::uint64_t> touched_;
793 std::vector<Coord3> path_;
796 std::vector<std::uint8_t> chunk_seen_;
798 bool has_goal_ =
false;
799 std::uint64_t residency_fingerprint_ = 0;
812 template <
typename World>
813 void stamp_residency(
const World& world)
noexcept {
814 if constexpr (!std::is_same_v<
typename World::residency_type,
816 residency_fingerprint_ = world.residency_fingerprint();
819 template <
typename World>
820 [[nodiscard]]
auto residency_matches(
const World& world)
const noexcept
822 if constexpr (std::is_same_v<
typename World::residency_type,
826 return residency_fingerprint_ == world.residency_fingerprint();
837 void reserve_requests(std::size_t request_count) {
838 results_.reserve(request_count);
839 offsets_.reserve(request_count);
840 sizes_.reserve(request_count);
841 processed_.reserve(request_count);
842 request_goal_.reserve(request_count);
843 goal_coords_.reserve(request_count);
844 goal_counts_.reserve(request_count);
847 void reserve_path_nodes(std::size_t node_count) {
848 paths_.reserve(node_count);
851 void reserve_search_nodes(std::size_t node_count) {
852 field_scratch_.reserve_nodes(node_count);
853 astar_scratch_.reserve_nodes(node_count);
856 void clear()
noexcept {
870 template <
typename World,
typename Class, std::u
int32_t MaxCost>
871 friend auto weighted_path_batch(
const World& world,
872 std::span<const PathRequest> requests,
874 -> std::span<const PathResult>;
878 std::vector<PathResult> results_;
879 std::vector<std::size_t> offsets_;
880 std::vector<std::size_t> sizes_;
881 std::vector<std::uint8_t> processed_;
882 std::vector<Coord3> paths_;
885 std::vector<std::uint32_t> goal_slots_;
886 std::vector<Coord3> goal_coords_;
887 std::vector<std::uint32_t> goal_counts_;
888 std::vector<std::uint32_t> request_goal_;
893 std::vector<std::uint32_t> group_offsets_;
894 std::vector<std::uint32_t> group_cursors_;
895 std::vector<std::uint32_t> group_members_;
898 std::vector<std::uint64_t> settle_targets_;
904enum class Axis : std::uint8_t {
910struct PortalRouteCandidate {
912 std::uint32_t score = 0;
913 std::size_t scan_tiles = 0;
920[[nodiscard]]
constexpr auto coord_hash(Coord3 coord)
noexcept
922 auto hash = std::uint64_t{0xcbf29ce484222325ull};
923 hash = (hash ^
static_cast<std::uint64_t
>(coord.x)) * 0x100000001b3ull;
924 hash = (hash ^
static_cast<std::uint64_t
>(coord.y)) * 0x100000001b3ull;
925 hash = (hash ^
static_cast<std::uint64_t
>(coord.z)) * 0x100000001b3ull;
926 hash = (hash ^ (hash >> 30u)) * 0xbf58476d1ce4e5b9ull;
927 hash = (hash ^ (hash >> 27u)) * 0x94d049bb133111ebull;
928 return hash ^ (hash >> 31u);
931[[nodiscard]]
constexpr auto manhattan(Coord3 lhs, Coord3 rhs)
noexcept
933 const auto distance = manhattan_distance(lhs, rhs);
934 if (distance > std::numeric_limits<std::uint32_t>::max()) {
935 return std::numeric_limits<std::uint32_t>::max();
937 return static_cast<std::uint32_t
>(distance);
940template <
typename World>
941[[nodiscard]]
constexpr auto tile_count() noexcept -> std::
size_t {
942 static_assert(World::chunk_count <= std::numeric_limits<std::size_t>::max() /
943 World::local_tile_count);
944 return static_cast<std::size_t
>(World::chunk_count * World::local_tile_count);
947template <
typename Shape>
948[[nodiscard]]
constexpr auto tile_index(Coord3 coord)
noexcept
950 static_assert(ShapeTraits<Shape>::tile_key_bits <= 64,
951 "MVP pathfinding supports shapes with u64 tile keys.");
952 return static_cast<std::uint64_t
>(tile_key<Shape>(coord).value);
955template <
typename Shape>
956[[nodiscard]]
constexpr auto tile_coord(std::uint64_t index)
noexcept
958 using Storage =
typename ShapeTraits<Shape>::TileKeyStorage;
959 return coord<Shape>(TileKey<Shape>{
static_cast<Storage
>(index)});
966template <
typename World,
typename ClassOrTag>
967[[nodiscard]]
auto is_passable(
const World& world, Coord3 coord)
noexcept
969 using Class = movement::movement_class_of<ClassOrTag>;
970 const auto resolved = world.try_resolve(coord);
971 if (!resolved.has_value()) {
974 if constexpr (std::is_same_v<
typename World::residency_type,
976 const auto* page = world.try_chunk(resolved->chunk_key);
977 return page !=
nullptr && Class::passable(*page, resolved->local_tile_id);
979 return Class::passable(world.chunk(resolved->chunk_key),
980 resolved->local_tile_id);
984template <
typename World,
typename ClassOrTag>
985[[nodiscard]]
auto is_passable_index(
const World& world,
986 std::uint64_t index)
noexcept ->
bool {
987 using Class = movement::movement_class_of<ClassOrTag>;
988 using Shape =
typename World::shape_type;
989 using Storage =
typename ShapeTraits<Shape>::TileKeyStorage;
990 const auto key = TileKey<Shape>{
static_cast<Storage
>(index)};
991 if constexpr (std::is_same_v<
typename World::residency_type,
996 const auto* page = world.try_chunk(chunk_key<Shape>(key));
997 if (page ==
nullptr) {
1000 return Class::passable(*page, local_tile_id<Shape>(key));
1002 return Class::passable(world.chunk(chunk_key<Shape>(key)),
1003 local_tile_id<Shape>(key));
1011template <
typename World,
typename Class>
1012[[nodiscard]]
auto tile_entry_cost_index(
const World& world,
1013 std::uint64_t index)
noexcept
1015 static_assert(std::derived_from<Class, movement::movement_class_tag>,
1016 "tile_entry_cost_index requires a MovementClass; wrap legacy "
1017 "tags in movement::LegacyWeighted<PassableTag, CostTag>.");
1018 using Shape =
typename World::shape_type;
1019 using Storage =
typename ShapeTraits<Shape>::TileKeyStorage;
1020 const auto key = TileKey<Shape>{
static_cast<Storage
>(index)};
1021 TESS_DIAG_EVENT(path_cost_read);
1022 return Class::entry_cost(world.chunk(chunk_key<Shape>(key)),
1023 local_tile_id<Shape>(key));
1026[[nodiscard]]
constexpr auto saturating_add(std::uint32_t lhs,
1027 std::uint32_t rhs)
noexcept
1029 if (rhs > std::numeric_limits<std::uint32_t>::max() - lhs) {
1030 return std::numeric_limits<std::uint32_t>::max();
1035template <
typename World,
typename Tag>
1036[[nodiscard]]
auto is_full_axis_barrier(
const World& world, Coord3 blocked,
1037 Axis axis)
noexcept ->
bool {
1038 using Shape =
typename World::shape_type;
1039 constexpr auto size = ShapeTraits<Shape>::size;
1041 if (axis == Axis::X) {
1042 for (std::int64_t z = 0; z < static_cast<std::int64_t>(size.z); ++z) {
1043 for (std::int64_t y = 0; y < static_cast<std::int64_t>(size.y); ++y) {
1044 const auto coord = Coord3{blocked.x, y, z};
1045 TESS_DIAG_EVENT(path_passability_check);
1046 if (is_passable_index<World, Tag>(world, tile_index<Shape>(coord))) {
1054 if (axis == Axis::Y) {
1055 for (std::int64_t z = 0; z < static_cast<std::int64_t>(size.z); ++z) {
1056 for (std::int64_t x = 0; x < static_cast<std::int64_t>(size.x); ++x) {
1057 const auto coord = Coord3{x, blocked.y, z};
1058 TESS_DIAG_EVENT(path_passability_check);
1059 if (is_passable_index<World, Tag>(world, tile_index<Shape>(coord))) {
1067 for (std::int64_t y = 0; y < static_cast<std::int64_t>(size.y); ++y) {
1068 for (std::int64_t x = 0; x < static_cast<std::int64_t>(size.x); ++x) {
1069 const auto coord = Coord3{x, y, blocked.z};
1070 TESS_DIAG_EVENT(path_passability_check);
1071 if (is_passable_index<World, Tag>(world, tile_index<Shape>(coord))) {
1079template <
typename Shape>
1080[[nodiscard]]
constexpr auto chunk_origin(ChunkCoord3 chunk)
noexcept
1082 constexpr auto size = ShapeTraits<Shape>::chunk;
1084 static_cast<std::int64_t
>(chunk.x * size.x),
1085 static_cast<std::int64_t
>(chunk.y * size.y),
1086 static_cast<std::int64_t
>(chunk.z * size.z),
1090template <
typename Shape>
1091[[nodiscard]]
constexpr auto adjacent_chunk(ChunkCoord3 from,
1092 ChunkCoord3 to)
noexcept ->
bool {
1093 const auto dx = from.x > to.x ? from.x - to.x : to.x - from.x;
1094 const auto dy = from.y > to.y ? from.y - to.y : to.y - from.y;
1095 const auto dz = from.z > to.z ? from.z - to.z : to.z - from.z;
1096 return dx + dy + dz == 1;
1099template <
typename World,
typename PassableTag>
1100[[nodiscard]]
auto best_chunk_portal(
const World& world, ChunkCoord3 from,
1101 ChunkCoord3 to, Coord3 current,
1102 Coord3 goal, Coord3& portal,
1103 std::size_t* scan_tiles =
nullptr) noexcept
1105 using Shape =
typename World::shape_type;
1106 constexpr auto chunk = ShapeTraits<Shape>::chunk;
1107 const auto origin = chunk_origin<Shape>(from);
1109 if (!adjacent_chunk<Shape>(from, to)) {
1114 auto best_score = std::numeric_limits<std::uint32_t>::max();
1115 const auto consider = [&](Coord3 source, Coord3 target) {
1116 if (scan_tiles !=
nullptr) {
1119 TESS_DIAG_EVENT(path_passability_check);
1120 if (!is_passable<World, PassableTag>(world, source)) {
1123 TESS_DIAG_EVENT(path_passability_check);
1124 if (!is_passable<World, PassableTag>(world, target)) {
1128 saturating_add(manhattan(current, target), manhattan(target, goal));
1129 if (!found || score < best_score) {
1136 if (from.x != to.x) {
1137 const auto step = from.x < to.x ? std::int64_t{1} : std::int64_t{-1};
1138 const auto source_x =
1139 step > 0 ? origin.x +
static_cast<std::int64_t
>(chunk.x) - 1 : origin.x;
1140 for (std::int64_t z = origin.z;
1141 z < origin.z +
static_cast<std::int64_t
>(chunk.z); ++z) {
1142 for (std::int64_t y = origin.y;
1143 y < origin.y +
static_cast<std::int64_t
>(chunk.y); ++y) {
1144 consider(Coord3{source_x, y, z}, Coord3{source_x + step, y, z});
1150 if (from.y != to.y) {
1151 const auto step = from.y < to.y ? std::int64_t{1} : std::int64_t{-1};
1152 const auto source_y =
1153 step > 0 ? origin.y +
static_cast<std::int64_t
>(chunk.y) - 1 : origin.y;
1154 for (std::int64_t z = origin.z;
1155 z < origin.z +
static_cast<std::int64_t
>(chunk.z); ++z) {
1156 for (std::int64_t x = origin.x;
1157 x < origin.x +
static_cast<std::int64_t
>(chunk.x); ++x) {
1158 consider(Coord3{x, source_y, z}, Coord3{x, source_y + step, z});
1164 const auto step = from.z < to.z ? std::int64_t{1} : std::int64_t{-1};
1165 const auto source_z =
1166 step > 0 ? origin.z +
static_cast<std::int64_t
>(chunk.z) - 1 : origin.z;
1167 for (std::int64_t y = origin.y;
1168 y < origin.y +
static_cast<std::int64_t
>(chunk.y); ++y) {
1169 for (std::int64_t x = origin.x;
1170 x < origin.x +
static_cast<std::int64_t
>(chunk.x); ++x) {
1171 consider(Coord3{x, y, source_z}, Coord3{x, y, source_z + step});
1177template <
typename World,
typename PassableTag>
1178[[nodiscard]]
auto build_chunk_portal_candidate(
const World& world,
1179 PathRequest request,
1180 std::span<const Axis> order,
1181 std::vector<Coord3>& waypoints)
1182 -> PortalRouteCandidate {
1183 using Shape =
typename World::shape_type;
1186 auto current = request.start;
1187 auto current_chunk = chunk_coord<Shape>(request.start);
1188 const auto goal_chunk = chunk_coord<Shape>(request.goal);
1189 auto result = PortalRouteCandidate{
true, 0, 0};
1191 const auto append_portal = [&](ChunkCoord3 next_chunk) {
1192 auto portal = Coord3{};
1193 if (!best_chunk_portal<World, PassableTag>(world, current_chunk, next_chunk,
1194 current, request.goal, portal,
1195 &result.scan_tiles)) {
1196 result.found =
false;
1199 result.score = saturating_add(result.score, manhattan(current, portal));
1200 waypoints.push_back(portal);
1202 current_chunk = next_chunk;
1206 for (
const auto axis : order) {
1207 if (axis == Axis::X) {
1208 while (current_chunk.x != goal_chunk.x) {
1209 auto next = current_chunk;
1210 if (current_chunk.x < goal_chunk.x) {
1215 if (!append_portal(next)) {
1219 }
else if (axis == Axis::Y) {
1220 while (current_chunk.y != goal_chunk.y) {
1221 auto next = current_chunk;
1222 if (current_chunk.y < goal_chunk.y) {
1227 if (!append_portal(next)) {
1232 while (current_chunk.z != goal_chunk.z) {
1233 auto next = current_chunk;
1234 if (current_chunk.z < goal_chunk.z) {
1239 if (!append_portal(next)) {
1246 result.score = saturating_add(result.score, manhattan(current, request.goal));
1250template <
typename Fn>
1251void for_each_axis_neighbor(Coord3 coord, Fn&& fn) {
1252 fn(Coord3{coord.x + 1, coord.y, coord.z});
1253 fn(Coord3{coord.x - 1, coord.y, coord.z});
1254 fn(Coord3{coord.x, coord.y + 1, coord.z});
1255 fn(Coord3{coord.x, coord.y - 1, coord.z});
1256 fn(Coord3{coord.x, coord.y, coord.z + 1});
1257 fn(Coord3{coord.x, coord.y, coord.z - 1});
1260template <
typename Shape,
typename Fn>
1261void for_each_indexed_axis_neighbor(Coord3 coord, std::uint64_t index,
1263 using Traits = ShapeTraits<Shape>;
1264 constexpr auto size = Traits::size;
1265 constexpr auto chunk = Traits::chunk;
1266 constexpr auto local_bits = Traits::local_bits;
1267 constexpr auto chunk_index_stride =
1268 local_bits >= 64 ? std::uint64_t{0} : (std::uint64_t{1} << local_bits);
1269 constexpr auto chunk_y_stride = Traits::chunk_count_x * chunk_index_stride;
1271 const auto local_x =
static_cast<std::uint64_t
>(coord.x) & (chunk.x - 1);
1272 const auto local_y =
static_cast<std::uint64_t
>(coord.y) & (chunk.y - 1);
1274 if constexpr (!Traits::degenerate_x) {
1275 if (
static_cast<std::uint64_t
>(coord.x) + 1 < size.x) {
1276 const auto next_index = local_x + 1 < chunk.x
1278 : index + chunk_index_stride - local_x;
1279 fn(Coord3{coord.x + 1, coord.y, coord.z}, next_index);
1282 const auto next_index =
1283 local_x > 0 ? index - 1 : index - chunk_index_stride + (chunk.x - 1);
1284 fn(Coord3{coord.x - 1, coord.y, coord.z}, next_index);
1288 if constexpr (!Traits::degenerate_y) {
1289 if (
static_cast<std::uint64_t
>(coord.y) + 1 < size.y) {
1290 const auto next_index = local_y + 1 < chunk.y
1292 : index + chunk_y_stride - local_y * chunk.x;
1293 fn(Coord3{coord.x, coord.y + 1, coord.z}, next_index);
1296 const auto next_index =
1297 local_y > 0 ? index - chunk.x
1298 : index - chunk_y_stride + (chunk.y - 1) * chunk.x;
1299 fn(Coord3{coord.x, coord.y - 1, coord.z}, next_index);
1303 if constexpr (!Traits::degenerate_z) {
1304 constexpr auto chunk_z_stride =
1305 Traits::chunk_count_x * Traits::chunk_count_y * chunk_index_stride;
1306 const auto local_xy = chunk.x * chunk.y;
1307 const auto local_z =
static_cast<std::uint64_t
>(coord.z) & (chunk.z - 1);
1308 if (
static_cast<std::uint64_t
>(coord.z) + 1 < size.z) {
1309 const auto next_index = local_z + 1 < chunk.z
1311 : index + chunk_z_stride - local_z * local_xy;
1312 fn(Coord3{coord.x, coord.y, coord.z + 1}, next_index);
1315 const auto next_index =
1316 local_z > 0 ? index - local_xy
1317 : index - chunk_z_stride + (chunk.z - 1) * local_xy;
1318 fn(Coord3{coord.x, coord.y, coord.z - 1}, next_index);
1323[[nodiscard]]
constexpr bool open_node_less(
1324 PathScratch::OpenNode lhs, PathScratch::OpenNode rhs)
noexcept {
1325 if (lhs.f != rhs.f) {
1326 return lhs.f > rhs.f;
1328 if (lhs.g != rhs.g) {
1329 return lhs.g < rhs.g;
1331 return lhs.index > rhs.index;
1336#include <tess/path/detail/astar.h>
1341template <
typename World,
typename PassableTag,
typename CostTag>
1345 using Shape =
typename World::shape_type;
1352 std::is_same_v<typename World::residency_type, AlwaysResident>,
1353 "build_weighted_route_product is dense-only; call weighted_astar_path "
1354 "directly for sparse worlds, or await the sparse route-cache slice.");
1358 weighted_astar_path<World, PassableTag, CostTag>(world, request, scratch);
1359 product.request_ = request;
1360 product.status_ = result.status;
1361 product.cost_ = result.cost;
1362 product.expanded_nodes_ = result.expanded_nodes;
1363 product.reached_nodes_ = result.reached_nodes;
1364 product.path_.assign(result.path.begin(), result.path.end());
1365 if (result.status == PathStatus::Found) {
1366 for (
const auto coord : product.path_) {
1367 const auto key = tile_key<Shape>(coord);
1368 product.dependencies_.add_chunk(world, chunk_key<Shape>(key));
1373 detail::capture_failure_dependencies<Shape>(world, request, result.status,
1374 product.dependencies_);
1377 return PathResult{product.status_, product.cost_, product.expanded_nodes_,
1378 product.reached_nodes_, product.path_};
1382template <
typename World>
1386 if (!product.is_valid(world)) {
1387 return PathResult{PathStatus::NoPath, 0, 0, 0, {}};
1389 return PathResult{product.status_, product.cost_, 0, 0, product.path_};
1396template <
typename World,
typename PassableTag,
typename CostTag>
1399 std::span<const Coord3> waypoints,
1403 using Shape =
typename World::shape_type;
1408 std::is_same_v<typename World::residency_type, AlwaysResident>,
1409 "build_weighted_portal_route_product is dense-only; chain "
1410 "weighted_astar_path directly for sparse worlds, or await the sparse "
1411 "route-cache slice.");
1413 std::vector<Coord3> stash;
1414 const auto source = product.stash_if_owned(waypoints, stash);
1417 product.request_ = request;
1418 product.waypoints_.assign(source.begin(), source.end());
1420 auto from = request.start;
1421 auto total_cost = std::uint32_t{0};
1422 auto total_expanded = std::size_t{0};
1423 auto total_reached = std::size_t{0};
1424 auto append_segment = [&](
PathRequest segment_request) {
1425 const auto result = weighted_astar_path<World, PassableTag, CostTag>(
1426 world, segment_request, scratch);
1427 total_expanded += result.expanded_nodes;
1428 total_reached += result.reached_nodes;
1429 if (result.status != PathStatus::Found) {
1430 product.path_.clear();
1431 product.status_ = result.status;
1432 product.expanded_nodes_ = total_expanded;
1433 product.reached_nodes_ = total_reached;
1436 detail::capture_failure_dependencies<Shape>(
1437 world, segment_request, result.status, product.dependencies_);
1440 total_cost = detail::saturating_add(total_cost, result.cost);
1441 product.segment_.assign(result.path.begin(), result.path.end());
1442 for (std::size_t i = product.path_.empty() ? 0u : 1u;
1443 i < product.segment_.size(); ++i) {
1444 product.path_.push_back(product.segment_[i]);
1449 for (
const auto waypoint : source) {
1450 if (!append_segment(
PathRequest{from, waypoint})) {
1451 return PathResult{product.status_, 0, total_expanded, total_reached,
1456 if (!append_segment(
PathRequest{from, request.goal})) {
1457 return PathResult{product.status_, 0, total_expanded, total_reached,
1461 product.status_ = PathStatus::Found;
1462 product.cost_ = total_cost;
1463 product.expanded_nodes_ = total_expanded;
1464 product.reached_nodes_ = total_reached;
1465 for (
const auto coord : product.path_) {
1466 const auto key = tile_key<Shape>(coord);
1467 product.dependencies_.add_chunk(world, chunk_key<Shape>(key));
1469 return PathResult{product.status_, product.cost_, product.expanded_nodes_,
1470 product.reached_nodes_, product.path_};
1474template <
typename World>
1478 if (!product.is_valid(world)) {
1479 return PathResult{PathStatus::NoPath, 0, 0, 0, {}};
1481 return PathResult{product.status_, product.cost_, 0, 0, product.path_};
1485template <
typename WorldType,
typename Tag>
1488 [[maybe_unused]] MissingChunkPolicy policy)
1490 using Shape =
typename WorldType::shape_type;
1491 using Space = detail::NodeIndexSpace<WorldType>;
1492 constexpr auto infinite_distance = std::numeric_limits<std::uint32_t>::max();
1494 TESS_DIAG_EVENT_VALUE(path_clear, scratch.touched_.size());
1495 scratch.clear_build();
1496 if (!contains<Shape>(goal)) {
1499 if constexpr (!Space::is_dense) {
1502 const Space residency{world};
1503 if (!residency.is_resident_index(detail::tile_index<Shape>(goal))) {
1505 ? PathStatus::Indeterminate
1506 : PathStatus::InvalidGoal,
1510 TESS_DIAG_EVENT(path_goal_passability_check);
1511 if (!detail::is_passable<WorldType, Tag>(world, goal)) {
1515 const Space space{world};
1516 const auto node_count = space.capacity_hint();
1517 if (scratch.distance_.size() != node_count) {
1518 TESS_DIAG_EVENT(path_initialize);
1519 scratch.generation_.assign(node_count, 0);
1520 scratch.distance_.assign(node_count, infinite_distance);
1523 const auto goal_index = detail::tile_index<Shape>(goal);
1524 const auto goal_offset = space.offset(goal_index);
1525 scratch.goal_ = goal;
1526 scratch.has_goal_ =
true;
1527 scratch.stamp_residency(world);
1528 scratch.distance_[goal_offset] = 0;
1529 scratch.touch_node(goal_offset, goal_index);
1530 TESS_DIAG_EVENT(path_touch_node);
1531 scratch.frontier_.push_back(goal_index);
1532 TESS_DIAG_EVENT(path_heap_push);
1534 std::size_t expanded_nodes = 0;
1535 std::size_t head = 0;
1538 [[maybe_unused]]
bool crossed_missing =
false;
1539 while (head < scratch.frontier_.size()) {
1540 const auto current = scratch.frontier_[head];
1542 TESS_DIAG_EVENT(path_heap_pop);
1545 const auto current_offset = space.offset(current);
1546 const auto current_distance =
1547 scratch.distance_at(current_offset, infinite_distance);
1548 const auto current_coord = detail::tile_coord<Shape>(current);
1549 detail::for_each_indexed_axis_neighbor<Shape>(
1550 current_coord, current, [&](
Coord3, std::uint64_t neighbor_index) {
1551 TESS_DIAG_EVENT(path_neighbor_candidate);
1552 if constexpr (!Space::is_dense) {
1555 if (!space.is_resident_index(neighbor_index)) {
1556 crossed_missing =
true;
1560 const auto neighbor_offset = space.offset(neighbor_index);
1561 if (scratch.is_current(neighbor_offset)) {
1562 TESS_DIAG_EVENT(path_neighbor_closed);
1565 TESS_DIAG_EVENT(path_passability_check);
1566 if (!detail::is_passable_index<WorldType, Tag>(world,
1568 TESS_DIAG_EVENT(path_neighbor_blocked);
1572 scratch.distance_[neighbor_offset] = current_distance + 1;
1573 scratch.touch_node(neighbor_offset, neighbor_index);
1574 TESS_DIAG_EVENT(path_touch_node);
1575 scratch.frontier_.push_back(neighbor_index);
1576 TESS_DIAG_EVENT(path_heap_push);
1580 if constexpr (!Space::is_dense) {
1581 if (crossed_missing && policy == MissingChunkPolicy::Indeterminate) {
1583 scratch.touched_.size()};
1587 scratch.touched_.size()};
1594template <
typename World,
typename Tag>
1597 using Shape =
typename World::shape_type;
1598 using Space = detail::NodeIndexSpace<World>;
1599 constexpr auto infinite_distance = std::numeric_limits<std::uint32_t>::max();
1601 scratch.clear_path();
1602 if (!contains<Shape>(start)) {
1603 return PathResult{PathStatus::InvalidStart, 0, 0, 0, scratch.path_};
1605 if constexpr (!Space::is_dense) {
1609 const Space residency{world};
1610 if (!residency.is_resident_index(detail::tile_index<Shape>(start))) {
1611 return PathResult{PathStatus::InvalidStart, 0, 0, 0, scratch.path_};
1614 TESS_DIAG_EVENT(path_start_passability_check);
1615 if (!detail::is_passable<World, Tag>(world, start)) {
1616 return PathResult{PathStatus::InvalidStart, 0, 0, 0, scratch.path_};
1618 if (!contains<Shape>(goal)) {
1619 return PathResult{PathStatus::InvalidGoal, 0, 0, 0, scratch.path_};
1621 if (!scratch.has_goal_ || scratch.goal_ != goal ||
1622 !scratch.residency_matches(world)) {
1623 return PathResult{PathStatus::NoPath, 0, 0, 0, scratch.path_};
1626 const Space space{world};
1627 const auto start_index = detail::tile_index<Shape>(start);
1628 auto current = start_index;
1629 auto current_offset = space.offset(current);
1630 auto current_distance =
1631 scratch.distance_at(current_offset, infinite_distance);
1632 if (current_distance == infinite_distance) {
1633 return PathResult{PathStatus::NoPath, 0, 0, scratch.touched_.size(),
1637 scratch.path_.push_back(start);
1638 TESS_DIAG_EVENT(path_reconstruct_node);
1639 while (current_distance > 0) {
1640 const auto current_coord = detail::tile_coord<Shape>(current);
1641 auto next = current;
1642 auto next_distance = current_distance;
1643 detail::for_each_indexed_axis_neighbor<Shape>(
1644 current_coord, current, [&](
Coord3, std::uint64_t neighbor_index) {
1645 if constexpr (!Space::is_dense) {
1649 if (!space.is_resident_index(neighbor_index)) {
1653 const auto neighbor_offset = space.offset(neighbor_index);
1654 const auto neighbor_distance =
1655 scratch.distance_at(neighbor_offset, infinite_distance);
1656 if (neighbor_distance < next_distance) {
1657 next = neighbor_index;
1658 next_distance = neighbor_distance;
1662 if (next == current || next_distance + 1 != current_distance) {
1663 scratch.path_.clear();
1664 return PathResult{PathStatus::NoPath, 0, 0, scratch.touched_.size(),
1669 current_offset = space.offset(current);
1670 current_distance = scratch.distance_at(current_offset, infinite_distance);
1671 scratch.path_.push_back(detail::tile_coord<Shape>(current));
1672 TESS_DIAG_EVENT(path_reconstruct_node);
1676 PathStatus::Found, scratch.distance_[space.offset(start_index)],
1677 scratch.path_.size(), scratch.touched_.size(), scratch.path_};
1681template <
typename WorldType,
typename Class>
1684 [[maybe_unused]] MissingChunkPolicy policy)
1686 static_assert(std::derived_from<Class, movement::movement_class_tag>,
1687 "build_weighted_distance_field<World, Class> requires a "
1688 "MovementClass; legacy tag pairs go through the "
1689 "<World, PassableTag, CostTag> overload.");
1690 using Shape =
typename WorldType::shape_type;
1691 using Space = detail::NodeIndexSpace<WorldType>;
1692 constexpr auto infinite_distance = std::numeric_limits<std::uint32_t>::max();
1694 TESS_DIAG_EVENT_VALUE(path_clear, scratch.touched_.size());
1695 scratch.clear_build();
1696 if (!contains<Shape>(goal)) {
1699 if constexpr (!Space::is_dense) {
1703 const Space residency{world};
1704 if (!residency.is_resident_index(detail::tile_index<Shape>(goal))) {
1706 ? PathStatus::Indeterminate
1707 : PathStatus::InvalidGoal,
1711 TESS_DIAG_EVENT(path_goal_passability_check);
1712 if (!detail::is_passable<WorldType, Class>(world, goal)) {
1716 const auto goal_index = detail::tile_index<Shape>(goal);
1717 if (detail::tile_entry_cost_index<WorldType, Class>(world, goal_index) == 0) {
1721 const Space space{world};
1722 const auto node_count = space.capacity_hint();
1723 if (scratch.distance_.size() != node_count) {
1724 TESS_DIAG_EVENT(path_initialize);
1725 scratch.generation_.assign(node_count, 0);
1726 scratch.distance_.assign(node_count, infinite_distance);
1729 const auto goal_offset = space.offset(goal_index);
1730 scratch.goal_ = goal;
1731 scratch.has_goal_ =
true;
1732 scratch.stamp_residency(world);
1733 scratch.distance_[goal_offset] = 0;
1734 scratch.touch_node(goal_offset, goal_index);
1735 TESS_DIAG_EVENT(path_touch_node);
1737 std::push_heap(scratch.weighted_frontier_.begin(),
1738 scratch.weighted_frontier_.end(), detail::open_node_less);
1739 TESS_DIAG_EVENT(path_heap_push);
1741 std::size_t expanded_nodes = 0;
1742 [[maybe_unused]]
bool crossed_missing =
false;
1743 while (!scratch.weighted_frontier_.empty()) {
1744 TESS_DIAG_EVENT(path_heap_pop);
1745 std::pop_heap(scratch.weighted_frontier_.begin(),
1746 scratch.weighted_frontier_.end(), detail::open_node_less);
1747 const auto current = scratch.weighted_frontier_.back();
1748 scratch.weighted_frontier_.pop_back();
1750 const auto current_offset = space.offset(current.index);
1751 const auto current_distance =
1752 scratch.distance_at(current_offset, infinite_distance);
1753 if (current.g != current_distance) {
1754 TESS_DIAG_EVENT_VALUE(path_skip_pop,
false);
1759 const auto current_entry_cost =
1760 detail::tile_entry_cost_index<WorldType, Class>(world, current.index);
1761 if (current_entry_cost == 0) {
1764 const auto current_coord = detail::tile_coord<Shape>(current.index);
1765 detail::for_each_indexed_axis_neighbor<Shape>(
1766 current_coord, current.index,
1767 [&](
Coord3, std::uint64_t neighbor_index) {
1768 TESS_DIAG_EVENT(path_neighbor_candidate);
1769 if constexpr (!Space::is_dense) {
1770 if (!space.is_resident_index(neighbor_index)) {
1771 crossed_missing = true;
1775 const auto neighbor_offset = space.offset(neighbor_index);
1776 TESS_DIAG_EVENT(path_relax_attempt);
1777 if (!scratch.is_current(neighbor_offset)) {
1778 TESS_DIAG_EVENT(path_passability_check);
1779 if (!detail::is_passable_index<WorldType, Class>(world,
1781 TESS_DIAG_EVENT(path_neighbor_blocked);
1784 if (detail::tile_entry_cost_index<WorldType, Class>(
1785 world, neighbor_index) == 0) {
1786 TESS_DIAG_EVENT(path_neighbor_blocked);
1789 scratch.distance_[neighbor_offset] = infinite_distance;
1790 scratch.touch_node(neighbor_offset, neighbor_index);
1791 TESS_DIAG_EVENT(path_touch_node);
1794 const auto next_distance =
1795 detail::saturating_add(current_distance, current_entry_cost);
1796 if (next_distance == infinite_distance) {
1800 scratch.distance_at(neighbor_offset, infinite_distance)) {
1801 TESS_DIAG_EVENT(path_relax_success);
1802 scratch.distance_[neighbor_offset] = next_distance;
1808 std::push_heap(scratch.weighted_frontier_.begin(),
1809 scratch.weighted_frontier_.end(),
1810 detail::open_node_less);
1811 TESS_DIAG_EVENT(path_heap_push);
1816 if constexpr (!Space::is_dense) {
1817 if (crossed_missing && policy == MissingChunkPolicy::Indeterminate) {
1818 return DistanceFieldResult{PathStatus::Indeterminate, expanded_nodes,
1819 scratch.touched_.size()};
1822 return DistanceFieldResult{PathStatus::Found, expanded_nodes,
1823 scratch.touched_.size()};
1826template <
typename World,
typename PassableTag,
typename CostTag>
1827auto build_weighted_distance_field(
const World& world, Coord3 goal,
1828 DistanceFieldScratch& scratch,
1829 MissingChunkPolicy policy)
1830 -> DistanceFieldResult {
1831 return build_weighted_distance_field<
1832 World, movement::LegacyWeighted<PassableTag, CostTag>>(world, goal,
1836#include <tess/path/detail/weighted_batch.h>
1840#include <tess/path/route_cache.h>
Definition field_product_cache.h:54
friend auto build_weighted_distance_field_in_box(const World &world, Coord3 goal, Box3 domain, DistanceFieldScratch &scratch, MissingChunkPolicy policy) -> DistanceFieldResult
Builds a weighted field restricted to the intersection with domain.
Definition distance_field_box.h:18
friend auto distance_field_product_path(const World &world, Coord3 start, const DistanceFieldProduct &product, DistanceFieldScratch &scratch) -> PathResult
Reconstructs a borrowed path from a valid multi-goal product.
Definition field_product_cache.h:526
friend auto distance_field_path(const World &world, Coord3 start, Coord3 goal, DistanceFieldScratch &scratch) -> PathResult
Definition path.h:1595
friend auto build_weighted_distance_field(const WorldType &world, Coord3 goal, DistanceFieldScratch &scratch, MissingChunkPolicy policy) -> DistanceFieldResult
Builds a movement-class weighted field into reusable caller scratch.
Definition path.h:1682
friend auto nearest_target(const World &world, Coord3 start, const DistanceFieldProduct &product, DistanceFieldScratch &scratch) -> NearestTargetResult
Finds the nearest reachable goal represented by a valid product.
Definition field_product_cache.h:602
friend auto build_distance_field_product(const World &world, const GoalSet &goals, DistanceFieldScratch &scratch, DistanceFieldProduct &product) -> DistanceFieldResult
Builds a dense multi-goal field into caller-owned reusable storage.
Definition field_product_cache.h:365
friend auto build_distance_field(const WorldType &world, Coord3 goal, DistanceFieldScratch &scratch, MissingChunkPolicy policy) -> DistanceFieldResult
Builds an unweighted goal-rooted field into caller-owned scratch.
Definition path.h:1486
Owns an ordered set of goals used to build a reusable distance product.
Definition field_product_cache.h:18
friend auto weighted_astar_path(const World &world, PathRequest request, PathScratch &scratch, MissingChunkPolicy policy) -> PathResult
Finds a weighted path using separate legacy passability and cost tags.
friend auto weighted_astar_path(const World &world, PathRequest request, PathScratch &scratch, MissingChunkPolicy policy) -> PathResult
Finds a weighted path using separate legacy passability and cost tags.
friend auto cached_astar_path(const World &world, PathRequest request, PathScratch &scratch, RouteCacheScratch &cache) -> PathResult
Runs unit A* with exact and same-goal suffix reuse from caller-owned cache.
Definition route_cache.h:432
friend auto astar_path(const World &world, PathRequest request, PathScratch &scratch, MissingChunkPolicy policy) -> PathResult
Definition path_view.h:21
Bounded scratch cache for exact and same-goal suffix unit routes.
Definition route_cache.h:42
friend auto build_weighted_chunk_portal_route_product(const World &world, PathRequest request, PathScratch &scratch, WeightedPortalRouteProduct &product) -> PathResult
Definition portal_route.h:112
friend auto weighted_portal_route_product_path(const World &world, const WeightedPortalRouteProduct &product) -> PathResult
Replays a portal-route product when its dependencies remain current.
Definition path.h:1475
friend auto build_weighted_portal_route_product(const World &world, PathRequest request, std::span< const Coord3 > waypoints, PathScratch &scratch, WeightedPortalRouteProduct &product) -> PathResult
Definition path.h:1397
Bounded, movement-class-safe cache of verified weighted path segments.
Definition portal_segment_cache.h:48
friend auto weighted_route_product_path(const World &world, const WeightedRouteProduct &product) -> PathResult
Replays a route product when all captured chunk versions still match.
Definition path.h:1383
friend auto build_weighted_route_product(const World &world, PathRequest request, PathScratch &scratch, WeightedRouteProduct &product) -> PathResult
Definition path.h:1342
Reports distance-field construction status and search work.
Definition path.h:72
Reports the closest reachable goal and a scratch-owned path to it.
Definition field_product_cache.h:41
Specifies inclusive start and goal coordinates for a path query.
Definition path.h:54