tess 0.4.0
Performance-first tile and path simulation substrate
Loading...
Searching...
No Matches
distance_field_box.h
1#pragma once
2
3#include <tess/path/path.h>
4
5#include <algorithm>
6#include <concepts>
7#include <cstddef>
8#include <cstdint>
9#include <limits>
10
11namespace tess {
12
17template <typename World, typename Class>
19 const World& world, Coord3 goal, Box3 domain, DistanceFieldScratch& scratch,
20 [[maybe_unused]] MissingChunkPolicy policy) -> DistanceFieldResult {
21 static_assert(std::derived_from<Class, movement::movement_class_tag>,
22 "build_weighted_distance_field_in_box<World, Class> requires "
23 "a MovementClass; legacy tag pairs go through the "
24 "<World, PassableTag, CostTag> overload.");
25 using Shape = typename World::shape_type;
26 using Space = detail::NodeIndexSpace<World>;
27 constexpr auto infinite_distance = std::numeric_limits<std::uint32_t>::max();
28
29 TESS_DIAG_EVENT_VALUE(path_clear, scratch.touched_.size());
30 scratch.clear_build();
31 if (!contains<Shape>(goal) || !contains(domain, goal)) {
32 return DistanceFieldResult{PathStatus::InvalidGoal, 0, 0};
33 }
34 if constexpr (!Space::is_dense) {
35 // A non-resident goal cannot seed the flood; resolve before is_passable /
36 // the entry-cost read index the goal chunk.
37 const Space residency{world};
38 if (!residency.is_resident_index(detail::tile_index<Shape>(goal))) {
39 return DistanceFieldResult{policy == MissingChunkPolicy::Indeterminate
40 ? PathStatus::Indeterminate
41 : PathStatus::InvalidGoal,
42 0, 0};
43 }
44 }
45 TESS_DIAG_EVENT(path_goal_passability_check);
46 if (!detail::is_passable<World, Class>(world, goal)) {
47 return DistanceFieldResult{PathStatus::InvalidGoal, 0, 0};
48 }
49
50 const auto goal_index = detail::tile_index<Shape>(goal);
51 if (detail::tile_entry_cost_index<World, Class>(world, goal_index) == 0) {
52 return DistanceFieldResult{PathStatus::InvalidGoal, 0, 0};
53 }
54
55 const Space space{world};
56 const auto node_count = space.capacity_hint();
57 if (scratch.distance_.size() != node_count) {
58 TESS_DIAG_EVENT(path_initialize);
59 scratch.generation_.assign(node_count, 0);
60 scratch.distance_.assign(node_count, infinite_distance);
61 }
62
63 const auto goal_offset = space.offset(goal_index);
64 scratch.goal_ = goal;
65 scratch.has_goal_ = true;
66 scratch.stamp_residency(world);
67 scratch.distance_[goal_offset] = 0;
68 scratch.touch_node(goal_offset, goal_index);
69 TESS_DIAG_EVENT(path_touch_node);
70 scratch.weighted_frontier_.push_back(PathScratch::OpenNode{goal_index, 0, 0});
71 std::push_heap(scratch.weighted_frontier_.begin(),
72 scratch.weighted_frontier_.end(), detail::open_node_less);
73 TESS_DIAG_EVENT(path_heap_push);
74
75 std::size_t expanded_nodes = 0;
76 [[maybe_unused]] bool crossed_missing = false;
77 while (!scratch.weighted_frontier_.empty()) {
78 TESS_DIAG_EVENT(path_heap_pop);
79 std::pop_heap(scratch.weighted_frontier_.begin(),
80 scratch.weighted_frontier_.end(), detail::open_node_less);
81 const auto current = scratch.weighted_frontier_.back();
82 scratch.weighted_frontier_.pop_back();
83
84 const auto current_offset = space.offset(current.index);
85 const auto current_distance =
86 scratch.distance_at(current_offset, infinite_distance);
87 if (current.g != current_distance) {
88 TESS_DIAG_EVENT_VALUE(path_skip_pop, false);
89 continue;
90 }
91 ++expanded_nodes;
92
93 const auto current_entry_cost =
94 detail::tile_entry_cost_index<World, Class>(world, current.index);
95 if (current_entry_cost == 0) {
96 continue;
97 }
98 const auto current_coord = detail::tile_coord<Shape>(current.index);
99 detail::for_each_indexed_axis_neighbor<Shape>(
100 current_coord, current.index,
101 [&](Coord3 neighbor_coord, std::uint64_t neighbor_index) {
102 if (!contains(domain, neighbor_coord)) {
103 return;
104 }
105 TESS_DIAG_EVENT(path_neighbor_candidate);
106 if constexpr (!Space::is_dense) {
107 if (!space.is_resident_index(neighbor_index)) {
108 crossed_missing = true;
109 return;
110 }
111 }
112 const auto neighbor_offset = space.offset(neighbor_index);
113 TESS_DIAG_EVENT(path_relax_attempt);
114 if (!scratch.is_current(neighbor_offset)) {
115 TESS_DIAG_EVENT(path_passability_check);
116 if (!detail::is_passable_index<World, Class>(world,
117 neighbor_index)) {
118 TESS_DIAG_EVENT(path_neighbor_blocked);
119 return;
120 }
121 if (detail::tile_entry_cost_index<World, Class>(
122 world, neighbor_index) == 0) {
123 TESS_DIAG_EVENT(path_neighbor_blocked);
124 return;
125 }
126 scratch.distance_[neighbor_offset] = infinite_distance;
127 scratch.touch_node(neighbor_offset, neighbor_index);
128 TESS_DIAG_EVENT(path_touch_node);
129 }
130
131 const auto next_distance =
132 detail::saturating_add(current_distance, current_entry_cost);
133 if (next_distance == infinite_distance) {
134 return;
135 }
136 if (next_distance <
137 scratch.distance_at(neighbor_offset, infinite_distance)) {
138 TESS_DIAG_EVENT(path_relax_success);
139 scratch.distance_[neighbor_offset] = next_distance;
140 scratch.weighted_frontier_.push_back(PathScratch::OpenNode{
141 neighbor_index,
142 next_distance,
143 next_distance,
144 });
145 std::push_heap(scratch.weighted_frontier_.begin(),
146 scratch.weighted_frontier_.end(),
147 detail::open_node_less);
148 TESS_DIAG_EVENT(path_heap_push);
149 }
150 });
151 }
152
153 if constexpr (!Space::is_dense) {
154 if (crossed_missing && policy == MissingChunkPolicy::Indeterminate) {
155 return DistanceFieldResult{PathStatus::Indeterminate, expanded_nodes,
156 scratch.touched_.size()};
157 }
158 }
159 return DistanceFieldResult{PathStatus::Found, expanded_nodes,
160 scratch.touched_.size()};
161}
162
163// Legacy <PassableTag, CostTag> forwarder: one movement class replaces the
164// tag pair; LegacyWeighted preserves the historical semantics exactly.
165template <typename World, typename PassableTag, typename CostTag>
166auto build_weighted_distance_field_in_box(const World& world, Coord3 goal,
167 Box3 domain,
168 DistanceFieldScratch& scratch,
169 MissingChunkPolicy policy)
170 -> DistanceFieldResult {
171 return build_weighted_distance_field_in_box<
172 World, movement::LegacyWeighted<PassableTag, CostTag>>(
173 world, goal, domain, scratch, policy);
174}
175
176} // namespace tess
Definition path.h:642
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
Definition world.h:22
Definition shape.h:75
Definition shape.h:30
Reports distance-field construction status and search work.
Definition path.h:72
Definition path.h:537
Definition shape.h:225