3#include <tess/path/path.h>
4#include <tess/path/portal_segment_cache.h>
17template <
typename World,
typename Class>
18[[nodiscard]]
auto weighted_endpoint_failure(
const World& world,
20 -> std::optional<PathStatus> {
21 using Shape =
typename World::shape_type;
22 if (!contains<Shape>(request.start) ||
23 !is_passable<World, Class>(world, request.start)) {
24 return PathStatus::InvalidStart;
26 if (!contains<Shape>(request.goal) ||
27 !is_passable<World, Class>(world, request.goal)) {
28 return PathStatus::InvalidGoal;
30 const auto start = tile_index<Shape>(request.start);
31 const auto goal = tile_index<Shape>(request.goal);
32 if (tile_entry_cost_index<World, Class>(world, start) == 0) {
33 return PathStatus::InvalidStart;
35 if (tile_entry_cost_index<World, Class>(world, goal) == 0) {
36 return PathStatus::InvalidGoal;
41template <
typename World,
typename Class>
42[[nodiscard]]
auto build_greedy_chunk_portal_candidate(
43 const World& world, PathRequest request, std::vector<Coord3>& waypoints)
44 -> PortalRouteCandidate {
45 using Shape =
typename World::shape_type;
48 auto current = request.start;
49 auto current_chunk = chunk_coord<Shape>(request.start);
50 const auto goal_chunk = chunk_coord<Shape>(request.goal);
51 auto result = PortalRouteCandidate{
true, 0, 0};
53 while (current_chunk != goal_chunk) {
54 auto found_step =
false;
55 auto best_score = std::numeric_limits<std::uint32_t>::max();
56 auto best_chunk = ChunkCoord3{};
57 auto best_portal = Coord3{};
59 const auto consider = [&](ChunkCoord3 next_chunk) {
60 auto portal = Coord3{};
61 auto scan_tiles = std::size_t{0};
62 if (!memoized_chunk_portal<World, Class>(world, current_chunk, next_chunk,
63 current, request.goal, portal,
65 result.scan_tiles += scan_tiles;
68 result.scan_tiles += scan_tiles;
69 const auto score = saturating_add(manhattan(current, portal),
70 manhattan(portal, request.goal));
71 if (!found_step || score < best_score) {
74 best_chunk = next_chunk;
79 if (current_chunk.x != goal_chunk.x) {
80 auto next = current_chunk;
81 if (current_chunk.x < goal_chunk.x) {
88 if (current_chunk.y != goal_chunk.y) {
89 auto next = current_chunk;
90 if (current_chunk.y < goal_chunk.y) {
97 if (current_chunk.z != goal_chunk.z) {
98 auto next = current_chunk;
99 if (current_chunk.z < goal_chunk.z) {
108 result.found =
false;
112 saturating_add(result.score, manhattan(current, best_portal));
113 waypoints.push_back(best_portal);
114 current = best_portal;
115 current_chunk = best_chunk;
118 result.score = saturating_add(result.score, manhattan(current, request.goal));
127template <
typename World,
typename Class>
128[[nodiscard]]
auto select_chunk_portal_waypoints(
129 const World& world, PathRequest request,
130 WeightedPortalRouteProduct& product) ->
bool {
131 constexpr auto orders = std::array{
132 std::array{Axis::X, Axis::Y, Axis::Z},
133 std::array{Axis::X, Axis::Z, Axis::Y},
134 std::array{Axis::Y, Axis::X, Axis::Z},
135 std::array{Axis::Y, Axis::Z, Axis::X},
136 std::array{Axis::Z, Axis::X, Axis::Y},
137 std::array{Axis::Z, Axis::Y, Axis::X},
144 const detail::PortalMemoScope memo_scope{detail::active_portal_memo()};
146 auto found_route =
false;
147 auto best_score = std::numeric_limits<std::uint32_t>::max();
148 product.best_waypoints_.clear();
149 for (
const auto& order : orders) {
150 const auto candidate = build_chunk_portal_candidate<World, Class>(
151 world, request, order, product.candidate_waypoints_);
152 ++product.route_candidates_;
153 product.portal_scan_tiles_ += candidate.scan_tiles;
154 if (!candidate.found) {
157 if (!found_route || candidate.score < best_score) {
159 best_score = candidate.score;
160 product.best_waypoints_.assign(product.candidate_waypoints_.begin(),
161 product.candidate_waypoints_.end());
165 const auto candidate = build_greedy_chunk_portal_candidate<World, Class>(
166 world, request, product.candidate_waypoints_);
167 ++product.route_candidates_;
168 product.portal_scan_tiles_ += candidate.scan_tiles;
169 if (candidate.found && (!found_route || candidate.score < best_score)) {
171 best_score = candidate.score;
172 product.best_waypoints_.assign(product.candidate_waypoints_.begin(),
173 product.candidate_waypoints_.end());
192template <
typename World,
typename Class>
196 using Shape =
typename World::shape_type;
201 std::is_same_v<typename World::residency_type, AlwaysResident>,
202 "build_weighted_chunk_portal_route_product requires an "
203 "AlwaysResidentWorld; use weighted_astar_path for sparse worlds.");
206 product.request_ = request;
208 if (
const auto failure =
209 detail::weighted_endpoint_failure<World, Class>(world, request)) {
210 product.status_ = *failure;
211 detail::capture_failure_dependencies<Shape>(world, request, product.status_,
212 product.dependencies_);
213 return PathResult{product.status_, 0, 0, 0, product.path_};
216 const auto found_route = detail::select_chunk_portal_waypoints<World, Class>(
217 world, request, product);
219 product.status_ = PathStatus::NoCandidate;
222 product.dependencies_.capture_all(world);
223 return PathResult{product.status_, 0, 0, 0, product.path_};
225 product.waypoints_.assign(product.best_waypoints_.begin(),
226 product.best_waypoints_.end());
228 auto from = request.start;
229 auto total_cost = std::uint64_t{0};
230 auto total_expanded = std::size_t{0};
231 auto total_reached = std::size_t{0};
232 auto append_segment = [&](
PathRequest segment_request) {
234 weighted_astar_path<World, Class>(world, segment_request, scratch);
235 total_expanded += result.expanded_nodes;
236 total_reached += result.reached_nodes;
237 if (result.status != PathStatus::Found) {
238 product.path_.clear();
239 product.status_ = PathStatus::NoCandidate;
240 product.expanded_nodes_ = total_expanded;
241 product.reached_nodes_ = total_reached;
244 detail::capture_failure_dependencies<Shape>(
245 world, request, product.status_, product.dependencies_);
248 total_cost += result.cost;
249 if (total_cost >= std::numeric_limits<std::uint32_t>::max()) {
250 product.path_.clear();
251 product.status_ = PathStatus::CostOverflow;
252 product.expanded_nodes_ = total_expanded;
253 product.reached_nodes_ = total_reached;
254 detail::capture_failure_dependencies<Shape>(
255 world, request, product.status_, product.dependencies_);
258 product.segment_.assign(result.path.begin(), result.path.end());
259 for (std::size_t i = product.path_.empty() ? 0u : 1u;
260 i < product.segment_.size(); ++i) {
261 product.path_.push_back(product.segment_[i]);
266 for (
const auto waypoint : product.waypoints_) {
267 if (!append_segment(
PathRequest{from, waypoint})) {
268 return PathResult{product.status_, 0, total_expanded, total_reached,
273 if (!append_segment(
PathRequest{from, request.goal})) {
274 return PathResult{product.status_, 0, total_expanded, total_reached,
278 product.status_ = PathStatus::Found;
279 product.cost_ =
static_cast<std::uint32_t
>(total_cost);
280 product.expanded_nodes_ = total_expanded;
281 product.reached_nodes_ = total_reached;
282 for (
const auto coord : product.path_) {
283 const auto key = tile_key<Shape>(coord);
284 product.dependencies_.add_chunk(world, chunk_key<Shape>(key));
286 return PathResult{product.status_, product.cost_, product.expanded_nodes_,
287 product.reached_nodes_, product.path_};
298template <
typename World,
typename Class>
304 std::is_same_v<typename World::residency_type, AlwaysResident>,
305 "build_weighted_chunk_portal_route_product_cached requires an "
306 "AlwaysResidentWorld; use weighted_astar_path for sparse worlds.");
309 product.request_ = request;
311 if (
const auto failure =
312 detail::weighted_endpoint_failure<World, Class>(world, request)) {
313 product.status_ = *failure;
314 return PathResult{product.status_, 0, 0, 0, product.path_};
317 if (!detail::select_chunk_portal_waypoints<World, Class>(world, request,
319 product.status_ = PathStatus::NoCandidate;
320 return PathResult{product.status_, 0, 0, 0, product.path_};
322 product.waypoints_.assign(product.best_waypoints_.begin(),
323 product.best_waypoints_.end());
325 auto class_cache = cache.template for_class<Class>();
326 auto from = request.start;
331 auto total_cost = std::uint64_t{0};
332 auto total_expanded = std::size_t{0};
333 auto total_reached = std::size_t{0};
334 auto append_segment = [&](
PathRequest segment_request) {
336 class_cache.lookup_append(world, segment_request, product.path_);
338 total_cost += hit.cost;
342 weighted_astar_path<World, Class>(world, segment_request, scratch);
343 class_cache.store(world, segment_request, result);
344 total_expanded += result.expanded_nodes;
345 total_reached += result.reached_nodes;
346 if (result.status != PathStatus::Found) {
347 product.path_.clear();
348 product.status_ = PathStatus::NoCandidate;
349 product.expanded_nodes_ = total_expanded;
350 product.reached_nodes_ = total_reached;
353 total_cost += result.cost;
354 for (std::size_t i = product.path_.empty() ? 0u : 1u;
355 i < result.path.size(); ++i) {
356 product.path_.push_back(result.path[i]);
361 for (
const auto waypoint : product.waypoints_) {
362 if (!append_segment(
PathRequest{from, waypoint})) {
363 return PathResult{product.status_, 0, total_expanded, total_reached,
368 if (!append_segment(
PathRequest{from, request.goal})) {
369 return PathResult{product.status_, 0, total_expanded, total_reached,
373 if (total_cost >= std::numeric_limits<std::uint32_t>::max()) {
374 product.path_.clear();
375 product.status_ = PathStatus::CostOverflow;
376 product.expanded_nodes_ = total_expanded;
377 product.reached_nodes_ = total_reached;
378 return PathResult{product.status_, 0, total_expanded, total_reached,
382 product.status_ = PathStatus::Found;
383 product.cost_ =
static_cast<std::uint32_t
>(total_cost);
384 product.expanded_nodes_ = total_expanded;
385 product.reached_nodes_ = total_reached;
386 return PathResult{product.status_, product.cost_, product.expanded_nodes_,
387 product.reached_nodes_, product.path_};
friend auto build_weighted_chunk_portal_route_product_cached(const World &world, PathRequest request, PathScratch &scratch, WeightedPortalSegmentCache &cache, WeightedPortalRouteProduct &product) -> PathResult
Builds a chunk-portal weighted route through the segment cache.
Definition portal_route.h:299
friend auto build_weighted_chunk_portal_route_product(const World &world, PathRequest request, PathScratch &scratch, WeightedPortalRouteProduct &product) -> PathResult
Definition portal_route.h:193
Definition portal_segment_cache.h:61
Specifies inclusive start and goal coordinates for a path query.
Definition request.h:10