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();
29 TESS_DIAG_EVENT_VALUE(path_clear, scratch.touched_.size());
30 scratch.clear_build();
31 if (!contains<Shape>(goal) || !contains(domain, goal)) {
34 if constexpr (!Space::is_dense) {
37 const Space residency{world};
38 if (!residency.is_resident_index(detail::tile_index<Shape>(goal))) {
40 ? PathStatus::Indeterminate
41 : PathStatus::InvalidGoal,
45 TESS_DIAG_EVENT(path_goal_passability_check);
46 if (!detail::is_passable<World, Class>(world, goal)) {
50 const auto goal_index = detail::tile_index<Shape>(goal);
51 if (detail::tile_entry_cost_index<World, Class>(world, goal_index) == 0) {
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);
63 const auto goal_offset = space.offset(goal_index);
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);
71 std::push_heap(scratch.weighted_frontier_.begin(),
72 scratch.weighted_frontier_.end(), detail::open_node_less);
73 TESS_DIAG_EVENT(path_heap_push);
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();
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);
93 const auto current_entry_cost =
94 detail::tile_entry_cost_index<World, Class>(world, current.index);
95 if (current_entry_cost == 0) {
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)) {
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;
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,
118 TESS_DIAG_EVENT(path_neighbor_blocked);
121 if (detail::tile_entry_cost_index<World, Class>(
122 world, neighbor_index) == 0) {
123 TESS_DIAG_EVENT(path_neighbor_blocked);
126 scratch.distance_[neighbor_offset] = infinite_distance;
127 scratch.touch_node(neighbor_offset, neighbor_index);
128 TESS_DIAG_EVENT(path_touch_node);
131 const auto next_distance =
132 detail::saturating_add(current_distance, current_entry_cost);
133 if (next_distance == infinite_distance) {
137 scratch.distance_at(neighbor_offset, infinite_distance)) {
138 TESS_DIAG_EVENT(path_relax_success);
139 scratch.distance_[neighbor_offset] = next_distance;
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);
153 if constexpr (!Space::is_dense) {
154 if (crossed_missing && policy == MissingChunkPolicy::Indeterminate) {
156 scratch.touched_.size()};
160 scratch.touched_.size()};