tess 1.0.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, typename Provider>
19 const World& world, Coord3 goal, Box3 domain, DistanceFieldScratch& scratch,
20 [[maybe_unused]] MissingChunkPolicy policy, const Provider& provider)
22 static_assert(std::derived_from<Class, movement::movement_class_tag>,
23 "build_weighted_distance_field_in_box<World, Class> requires "
24 "a MovementClass; pass a movement class such as "
25 "PositiveCostFieldMovement.");
26 using Shape = typename World::shape_type;
27 using Space = detail::NodeIndexSpace<World>;
29 constexpr auto infinite_distance = std::numeric_limits<std::uint32_t>::max();
30
31 TESS_DIAG_EVENT_VALUE(path_clear, scratch.touched_.size());
32 scratch.clear_build();
33 if (!contains<Shape>(goal) || !contains(domain, goal)) {
34 return DistanceFieldResult{PathStatus::InvalidGoal, 0, 0};
35 }
36 if constexpr (!Space::is_dense) {
37 // A non-resident goal cannot seed the flood; resolve before is_passable /
38 // the entry-cost read index the goal chunk.
39 const Space residency{world};
40 if (!residency.is_resident_index(detail::tile_index<Shape>(goal))) {
42 policy == MissingChunkPolicy::ReportIndeterminate
43 ? PathStatus::Indeterminate
44 : PathStatus::InvalidGoal,
45 0, 0};
46 }
47 }
48 TESS_DIAG_EVENT(path_goal_passability_check);
49 if (!detail::is_passable<World, Class>(world, goal)) {
50 return DistanceFieldResult{PathStatus::InvalidGoal, 0, 0};
51 }
52
53 const auto goal_index = detail::tile_index<Shape>(goal);
54 if (detail::tile_entry_cost_index<World, Class>(world, goal_index) == 0) {
55 return DistanceFieldResult{PathStatus::InvalidGoal, 0, 0};
56 }
57
58 const Space space{world};
59 const auto node_count = space.capacity_hint();
60 if (scratch.distance_.size() != node_count) {
61 TESS_DIAG_EVENT(path_initialize);
62 scratch.generation_.assign(node_count, 0);
63 scratch.distance_.assign(node_count, infinite_distance);
64 }
65
66 const auto goal_offset = space.offset(goal_index);
67 const auto model = Model{provider};
68 scratch.goal_ = goal;
69 scratch.has_goal_ = true;
70 scratch.template stamp_model<Model>(
71 model, detail::transition_provider_instance_identity(provider));
72 scratch.stamp_residency(world);
73 scratch.distance_[goal_offset] = 0;
74 scratch.touch_node(goal_offset, goal_index);
75 TESS_DIAG_EVENT(path_touch_node);
76 scratch.weighted_frontier_.push_back(
77 detail::PackedOpenNode::make(goal_index, 0, 0));
78 std::push_heap(scratch.weighted_frontier_.begin(),
79 scratch.weighted_frontier_.end(),
80 detail::packed_open_node_less);
81 TESS_DIAG_EVENT(path_heap_push);
82
83 std::size_t expanded_nodes = 0;
84 [[maybe_unused]] bool crossed_missing = false;
85 auto cost_overflow = false;
86 while (!scratch.weighted_frontier_.empty()) {
87 TESS_DIAG_EVENT(path_heap_pop);
88 std::pop_heap(scratch.weighted_frontier_.begin(),
89 scratch.weighted_frontier_.end(),
90 detail::packed_open_node_less);
91 const auto current = scratch.weighted_frontier_.back();
92 scratch.weighted_frontier_.pop_back();
93
94 const auto current_offset = space.offset(current.index);
95 const auto current_distance =
96 scratch.distance_at(current_offset, infinite_distance);
97 if (current.g() != current_distance) {
98 TESS_DIAG_EVENT_VALUE(path_skip_pop, false);
99 continue;
100 }
101 ++expanded_nodes;
102
103 const auto current_coord = detail::tile_coord<Shape>(current.index);
104 model.for_each_reverse(
105 world, current_coord, current.index, [&](auto probe) {
106 if (!contains(domain, probe.to)) {
107 return;
108 }
109 TESS_DIAG_EVENT(path_neighbor_candidate);
110 if (probe.availability == TransitionAvailability::MissingTopology) {
111 crossed_missing = true;
112 return;
113 }
114 if (probe.cost_overflow) {
115 cost_overflow = true;
116 return;
117 }
118 const auto neighbor_index = probe.to_index;
119 if constexpr (!Space::is_dense) {
120 if (!space.is_resident_index(neighbor_index)) {
121 crossed_missing = true;
122 return;
123 }
124 }
125 const auto neighbor_offset = space.offset(neighbor_index);
126 TESS_DIAG_EVENT(path_relax_attempt);
127 if (!scratch.is_current(neighbor_offset)) {
128 scratch.distance_[neighbor_offset] = infinite_distance;
129 scratch.touch_node(neighbor_offset, neighbor_index);
130 TESS_DIAG_EVENT(path_touch_node);
131 }
132
133 const auto next_distance =
134 detail::saturating_add(current_distance, probe.cost);
135 if (next_distance == infinite_distance) {
136 cost_overflow = true;
137 return;
138 }
139 if (next_distance <
140 scratch.distance_at(neighbor_offset, infinite_distance)) {
141 TESS_DIAG_EVENT(path_relax_success);
142 scratch.distance_[neighbor_offset] = next_distance;
143 scratch.weighted_frontier_.push_back(detail::PackedOpenNode::make(
144 neighbor_index, next_distance, next_distance));
145 std::push_heap(scratch.weighted_frontier_.begin(),
146 scratch.weighted_frontier_.end(),
147 detail::packed_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::ReportIndeterminate) {
155 scratch.publish_build_status(PathStatus::Indeterminate);
156 return DistanceFieldResult{PathStatus::Indeterminate, expanded_nodes,
157 scratch.touched_.size()};
158 }
159 }
160 if (cost_overflow) {
161 scratch.discard_build_result();
162 return DistanceFieldResult{PathStatus::CostOverflow, expanded_nodes,
163 scratch.touched_.size()};
164 }
165 scratch.publish_build_status(PathStatus::Found);
166 return DistanceFieldResult{PathStatus::Found, expanded_nodes,
167 scratch.touched_.size()};
168}
169
170template <typename World, typename Class>
172 const World& world, Coord3 goal, Box3 domain, DistanceFieldScratch& scratch,
173 MissingChunkPolicy policy) -> DistanceFieldResult {
176 world, goal, domain, scratch, policy, AdjacentTransitions{});
177}
178
179} // namespace tess
Definition path.h:872
friend auto build_weighted_distance_field_in_box(const World &world, Coord3 goal, Box3 domain, DistanceFieldScratch &scratch, MissingChunkPolicy policy, const Provider &provider) -> DistanceFieldResult
Builds a boxed weighted field composed with a special provider.
Definition distance_field_box.h:18
Definition transition_model.h:380
Definition world.h:22
Supplies no special transitions beyond ordinary face adjacency.
Definition transition_provider.h:132
Definition shape.h:94
Definition shape.h:46
Reports distance-field construction status and search work.
Definition path.h:70
Definition shape.h:296