/** * Search for a path to the destination. * @return Whether a path has been found. */ bool PathSearcher::Search() { this->dest_pos = nullptr; while (!open_points.empty()) { WalkedDistance wd = *open_points.begin(); open_points.erase(open_points.begin()); if (wd.traveled != wd.pos->traveled || wd.estimate != wd.pos->estimate) continue; // Invalid open point. /* Reached the destination? */ const WalkedPosition *wp = wd.pos; if (wp->cur_vox == this->dest_vox) { this->dest_pos = wp; return true; } /* Add new open points. */ const Voxel *v = _world.GetVoxel(wp->cur_vox); if (v == nullptr) continue; // No voxel at the expected point, don't bother. uint8 exits = GetPathExits(v); for (TileEdge edge = EDGE_BEGIN; edge < EDGE_COUNT; edge++) { if ((exits & (0x11 << edge)) == 0) continue; /* There is an outgoing connection, is it also on the world? */ Point16 dxy = _tile_dxy[edge]; if (dxy.x < 0 && wp->cur_vox.x == 0) continue; if (dxy.x > 0 && wp->cur_vox.x + 1 == _world.GetXSize()) continue; if (dxy.y < 0 && wp->cur_vox.y == 0) continue; if (dxy.y > 0 && wp->cur_vox.y + 1 == _world.GetYSize()) continue; int extra_z = ((exits & (0x10 << edge)) != 0); if (wp->cur_vox.z + extra_z < 0 || wp->cur_vox.z + extra_z >= WORLD_Z_SIZE) continue; /* Now check the other side, new_z is the voxel where the path should be at the bottom. */ const Voxel *v2 = _world.GetVoxel(wp->cur_vox + XYZPoint16(dxy.x, dxy.y, extra_z)); if (v2 == nullptr) continue; uint8 other_exits = GetPathExits(v2); if ((other_exits & (1 << ((edge + 2) % 4))) == 0) { // No path here, try one voxel below extra_z--; if (wp->cur_vox.z + extra_z < 0) continue; v2 = _world.GetVoxel(wp->cur_vox + XYZPoint16(dxy.x, dxy.y, extra_z)); if (v2 == nullptr) continue; other_exits = GetPathExits(v2); if ((other_exits & (0x10 << ((edge + 2) % 4))) == 0) continue; } /* Add new open point to the path finder. */ this->AddOpen(wp->cur_vox + XYZPoint16(dxy.x, dxy.y, extra_z), wp->traveled + 1, wp); } } return false; }
/** * Walk over a queue path from the given entry edge at the given position. * If it leads to a new voxel edge, the provided position and edge is update with the exit point. * @param voxel_pos [inout] Start voxel position before the queue path, updated to last voxel position. * @param entry Direction used for entry to the path, updated to last edge exit direction. * @return Whether a (possibly) new last voxel could be found, \c false means the path leads to nowhere. * @note Parameter values may get changed during the call, do not rely on their values except when \c true is returned. */ bool TravelQueuePath(XYZPoint16 *voxel_pos, TileEdge *entry) { XYZPoint16 new_pos = *voxel_pos; TileEdge edge = *entry; /* Check that entry voxel actually exists. */ if (!IsVoxelstackInsideWorld(new_pos.x, new_pos.y)) return false; for (;;) { new_pos.x += _tile_dxy[edge].x; new_pos.y += _tile_dxy[edge].y; if (!IsVoxelstackInsideWorld(new_pos.x, new_pos.y)) return false; const Voxel *vx = _world.GetVoxel(new_pos); if (vx == nullptr || !HasValidPath(vx)) { /* No path here, check the voxel below. */ if (new_pos.z == 0) return true; // Path ends here. new_pos.z--; vx = _world.GetVoxel(new_pos); if (vx == nullptr || !HasValidPath(vx)) return true; // Path ends here. } if (new_pos == *voxel_pos) return false; // Cycle detected. /* Stop if we found a non-queue path. */ if (_sprite_manager.GetPathStatus(GetPathType(vx->GetInstanceData())) != PAS_QUEUE_PATH) return true; /* At this point: * voxel_pos, edge (and *entry) contain the last valid voxel edge. * new_pos, vx is the next queue path tile position. */ uint8 exits = GetPathExits(vx); /* Check that the new tile can go back to our last tile. */ uint8 rev_edge = (edge + 2) % 4; if (!((exits & (0x01 << rev_edge)) != 0 && new_pos.z == voxel_pos->z) && !((exits & (0x10 << rev_edge)) != 0 && new_pos.z == voxel_pos->z - 1)) { return false; } /* Find exit to the next path tile. */ for (edge = EDGE_BEGIN; edge < EDGE_COUNT; edge++) { if (edge == rev_edge) continue; // Skip the direction we came from. if ((exits & (0x01 << edge)) != 0) break; if ((exits & (0x10 << edge)) != 0) { new_pos.z++; break; } } if (edge == EDGE_COUNT) return false; // Queue path doesn't have a second exit. *voxel_pos = new_pos; *entry = edge; } }
/** * Find all edges that are an exit for a path in the given voxel. No investigation is performed whether the exits connect to anything. * @param v %Voxel to investigate. * @return Exits for a path in the queried voxel. Lower 4 bits are exits at the bottom; upper 4 bits are exits at the top. */ uint8 GetPathExits(const Voxel *v) { SmallRideInstance instance = v->GetInstance(); if (instance != SRI_PATH) return 0; uint16 inst_data = v->GetInstanceData(); if (!HasValidPath(inst_data)) return 0; return GetPathExits(GetImplodedPathSlope(inst_data), true); }