195 static constexpr std::uint32_t maximum_transition_cost = 1;
197 template <
typename World,
typename Sink>
200 using Shape =
typename World::shape_type;
203 World::schema_type::template contains<StairTag>,
204 "StairTransitions references a field absent from the schema.");
205 emit_for_feet(world, chunk, chunk, sink);
210 const auto coord_of_chunk = chunk_coord<Shape>(chunk);
211 const auto scan_foot_chunk = [&](
ChunkCoord3 neighbor) {
212 const auto key = chunk_key<Shape>(neighbor);
213 if constexpr (std::is_same_v<
typename World::residency_type,
218 if (!world.is_resident(key)) {
222 emit_for_feet(world, key, chunk, sink);
224 if (coord_of_chunk.z > 0) {
225 auto below = coord_of_chunk;
227 scan_foot_chunk(below);
229 if (coord_of_chunk.x > 0) {
230 auto west = coord_of_chunk;
232 scan_foot_chunk(west);
234 if (coord_of_chunk.x + 1 < Traits::chunk_count_x) {
235 auto east = coord_of_chunk;
237 scan_foot_chunk(east);
239 if (coord_of_chunk.y > 0) {
240 auto south = coord_of_chunk;
242 scan_foot_chunk(south);
244 if (coord_of_chunk.y + 1 < Traits::chunk_count_y) {
245 auto north = coord_of_chunk;
247 scan_foot_chunk(north);
252 template <
typename World,
typename Sink>
254 for_each_from(world, from, sink);
258 template <
typename World,
typename Sink>
262 for_each_from(world, to, sink);
266 template <
typename World>
267 [[nodiscard]]
static auto try_landing(
const World& world,
Coord3 foot,
268 Coord3& landing) ->
bool {
269 using Shape =
typename World::shape_type;
270 if (!contains<Shape>(foot)) {
273 const auto resolved = world.resolve(foot);
274 const auto* page = world.try_chunk(resolved.chunk_key);
275 if (page ==
nullptr) {
278 const auto value = page->template field<StairTag>(resolved.local_tile_id);
279 static_assert(std::is_integral_v<std::remove_cvref_t<
decltype(value)>>,
280 "StairTransitions requires an integral stair field.");
281 if constexpr (std::is_signed_v<std::remove_cvref_t<
decltype(value)>>) {
285 }
else if (value == 0) {
288 if (
static_cast<std::uint64_t
>(value) >
289 static_cast<std::uint64_t
>(StairDirection::NegativeY)) {
295 switch (
static_cast<StairDirection
>(
static_cast<std::uint8_t
>(value))) {
296 case StairDirection::PositiveX:
299 case StairDirection::NegativeX:
302 case StairDirection::PositiveY:
305 case StairDirection::NegativeY:
308 case StairDirection::None:
311 if (!contains<Shape>(landing)) {
314 const auto foot_chunk = chunk_coord<Shape>(foot);
315 const auto landing_chunk = chunk_coord<Shape>(landing);
316 const auto crossings = (foot_chunk.x != landing_chunk.x ? 1 : 0) +
317 (foot_chunk.y != landing_chunk.y ? 1 : 0) +
318 (foot_chunk.z != landing_chunk.z ? 1 : 0);
319 return crossings <= 1;
322 template <
typename World,
typename Sink>
323 static void for_each_from(
const World& world, Coord3 from, Sink&& sink) {
324 using Shape =
typename World::shape_type;
325 if (!contains<Shape>(from)) {
329 auto landing = Coord3{};
330 if (try_landing(world, from, landing)) {
331 sink(SpecialTransitionCandidate{.to = landing});
337 const auto probe_foot = [&](Coord3 foot) {
338 if (!contains<Shape>(foot)) {
341 const auto resolved = world.resolve(foot);
342 if (world.try_chunk(resolved.chunk_key) ==
nullptr) {
343 sink(SpecialTransitionCandidate{.to = foot, .missing_topology =
true});
346 auto candidate_landing = Coord3{};
347 if (try_landing(world, foot, candidate_landing) &&
348 candidate_landing == from) {
349 sink(SpecialTransitionCandidate{.to = foot});
352 probe_foot(Coord3{from.x - 1, from.y, from.z - 1});
353 probe_foot(Coord3{from.x + 1, from.y, from.z - 1});
354 probe_foot(Coord3{from.x, from.y - 1, from.z - 1});
355 probe_foot(Coord3{from.x, from.y + 1, from.z - 1});
361 template <
typename World,
typename Sink>
362 static void emit_for_feet(
const World& world, ChunkKey foot_chunk,
363 ChunkKey origin_chunk, Sink&& sink) {
364 using Shape =
typename World::shape_type;
365 using Traits = ShapeTraits<Shape>;
366 const auto& page = world.chunk(foot_chunk);
367 const auto origin = chunk_coord<Shape>(foot_chunk);
368 const auto base = Coord3{
369 static_cast<std::int64_t
>(origin.x * Traits::chunk.x),
370 static_cast<std::int64_t
>(origin.y * Traits::chunk.y),
371 static_cast<std::int64_t
>(origin.z * Traits::chunk.z),
373 for (std::uint64_t raw_id = 0; raw_id < Traits::local_tile_count;
375 const auto value = page.template field<StairTag>(LocalTileId{raw_id});
376 static_assert(std::is_integral_v<std::remove_cvref_t<
decltype(value)>>,
377 "StairTransitions requires an integral stair field.");
381 if constexpr (std::is_signed_v<std::remove_cvref_t<
decltype(value)>>) {
385 }
else if (value == 0) {
388 if (
static_cast<std::uint64_t
>(value) >
389 static_cast<std::uint64_t
>(StairDirection::NegativeY)) {
392 const auto direction =
393 static_cast<StairDirection
>(
static_cast<std::uint8_t
>(value));
394 const auto local = local_coord_of<Shape>(raw_id);
396 Coord3{base.x + local.x, base.y + local.y, base.z + local.z};
400 case StairDirection::PositiveX:
403 case StairDirection::NegativeX:
406 case StairDirection::PositiveY:
409 case StairDirection::NegativeY:
412 case StairDirection::None:
415 if (!contains<Shape>(landing)) {
420 const auto foot_chunk_coord = chunk_coord<Shape>(foot);
421 const auto landing_chunk_coord = chunk_coord<Shape>(landing);
422 const auto dx = foot_chunk_coord.x != landing_chunk_coord.x ? 1 : 0;
423 const auto dy = foot_chunk_coord.y != landing_chunk_coord.y ? 1 : 0;
424 const auto dz = foot_chunk_coord.z != landing_chunk_coord.z ? 1 : 0;
425 if (dx + dy + dz > 1) {
428 const auto landing_chunk = chunk_key<Shape>(landing_chunk_coord);
429 if (foot_chunk.value == origin_chunk.value) {
431 if (landing_chunk.value == origin_chunk.value) {
434 }
else if (landing_chunk.value == origin_chunk.value) {
440 template <
typename Shape>
441 [[nodiscard]]
static auto local_coord_of(std::uint64_t raw_id)
noexcept
443 const auto chunk = ShapeTraits<Shape>::chunk;
444 const auto xy =
static_cast<std::uint64_t
>(chunk.x) * chunk.y;
445 const auto z = raw_id / xy;
446 const auto rest = raw_id % xy;
448 static_cast<std::int64_t
>(rest % chunk.x),
449 static_cast<std::int64_t
>(rest / chunk.x),
450 static_cast<std::int64_t
>(z),