You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
581 lines
17 KiB
581 lines
17 KiB
|
6 years ago
|
/**
|
||
|
|
* @file path.cpp
|
||
|
|
*
|
||
|
|
* Implementation of the path finding algorithms.
|
||
|
|
*/
|
||
|
4 years ago
|
#include "engine/path.h"
|
||
|
5 years ago
|
|
||
|
4 years ago
|
#include <array>
|
||
|
|
|
||
|
4 years ago
|
#include "levels/gendung.h"
|
||
|
5 years ago
|
#include "objects.h"
|
||
|
8 years ago
|
|
||
|
5 years ago
|
namespace devilution {
|
||
|
5 years ago
|
namespace {
|
||
|
5 years ago
|
|
||
|
4 years ago
|
constexpr size_t MaxPathNodes = 300;
|
||
|
|
|
||
|
|
struct PathNode {
|
||
|
|
static constexpr uint16_t InvalidIndex = std::numeric_limits<uint16_t>::max();
|
||
|
|
static constexpr size_t MaxChildren = 8;
|
||
|
|
|
||
|
|
int16_t x = 0;
|
||
|
|
int16_t y = 0;
|
||
|
|
uint16_t parentIndex = InvalidIndex;
|
||
|
|
uint16_t childIndices[MaxChildren] = { InvalidIndex, InvalidIndex, InvalidIndex, InvalidIndex, InvalidIndex, InvalidIndex, InvalidIndex, InvalidIndex };
|
||
|
|
uint16_t nextNodeIndex = InvalidIndex;
|
||
|
|
uint8_t f = 0;
|
||
|
|
uint8_t h = 0;
|
||
|
|
uint8_t g = 0;
|
||
|
|
|
||
|
|
[[nodiscard]] Point position() const
|
||
|
|
{
|
||
|
|
return Point { x, y };
|
||
|
|
}
|
||
|
|
|
||
|
|
void addChild(uint16_t childIndex)
|
||
|
|
{
|
||
|
|
size_t index = 0;
|
||
|
|
for (; index < MaxChildren; ++index) {
|
||
|
|
if (childIndices[index] == InvalidIndex)
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
assert(index < MaxChildren);
|
||
|
|
childIndices[index] = childIndex;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
PathNode PathNodes[MaxPathNodes];
|
||
|
|
|
||
|
5 years ago
|
/** A linked list of the A* frontier, sorted by distance */
|
||
|
4 years ago
|
PathNode *Path2Nodes;
|
||
|
|
|
||
|
5 years ago
|
/**
|
||
|
|
* @brief return a node for a position on the frontier, or NULL if not found
|
||
|
|
*/
|
||
|
4 years ago
|
uint16_t GetNode1(Point targetPosition)
|
||
|
5 years ago
|
{
|
||
|
4 years ago
|
uint16_t result = Path2Nodes->nextNodeIndex;
|
||
|
|
while (result != PathNode::InvalidIndex) {
|
||
|
|
if (PathNodes[result].position() == targetPosition)
|
||
|
5 years ago
|
return result;
|
||
|
4 years ago
|
result = PathNodes[result].nextNodeIndex;
|
||
|
5 years ago
|
}
|
||
|
4 years ago
|
return PathNode::InvalidIndex;
|
||
|
5 years ago
|
}
|
||
|
5 years ago
|
|
||
|
6 years ago
|
/**
|
||
|
4 years ago
|
* @brief insert `front` node into the frontier (keeping the frontier sorted by total distance)
|
||
|
8 years ago
|
*/
|
||
|
4 years ago
|
void NextNode(uint16_t front)
|
||
|
5 years ago
|
{
|
||
|
4 years ago
|
if (Path2Nodes->nextNodeIndex == PathNode::InvalidIndex) {
|
||
|
|
Path2Nodes->nextNodeIndex = front;
|
||
|
5 years ago
|
return;
|
||
|
|
}
|
||
|
8 years ago
|
|
||
|
4 years ago
|
PathNode *current = Path2Nodes;
|
||
|
|
uint16_t nextIndex = Path2Nodes->nextNodeIndex;
|
||
|
|
const uint8_t maxF = PathNodes[front].f;
|
||
|
|
while (nextIndex != PathNode::InvalidIndex && PathNodes[nextIndex].f < maxF) {
|
||
|
|
current = &PathNodes[nextIndex];
|
||
|
|
nextIndex = current->nextNodeIndex;
|
||
|
5 years ago
|
}
|
||
|
4 years ago
|
PathNodes[front].nextNodeIndex = nextIndex;
|
||
|
|
current->nextNodeIndex = front;
|
||
|
5 years ago
|
}
|
||
|
8 years ago
|
|
||
|
5 years ago
|
/** A linked list of all visited nodes */
|
||
|
4 years ago
|
PathNode *VisitedNodes;
|
||
|
|
|
||
|
6 years ago
|
/**
|
||
|
5 years ago
|
* @brief return a node for this position if it was visited, or NULL if not found
|
||
|
8 years ago
|
*/
|
||
|
4 years ago
|
uint16_t GetNode2(Point targetPosition)
|
||
|
5 years ago
|
{
|
||
|
4 years ago
|
uint16_t result = VisitedNodes->nextNodeIndex;
|
||
|
|
while (result != PathNode::InvalidIndex) {
|
||
|
|
if (PathNodes[result].position() == targetPosition)
|
||
|
5 years ago
|
return result;
|
||
|
4 years ago
|
result = PathNodes[result].nextNodeIndex;
|
||
|
5 years ago
|
}
|
||
|
4 years ago
|
return result;
|
||
|
5 years ago
|
}
|
||
|
|
|
||
|
5 years ago
|
/**
|
||
|
|
* @brief get the next node on the A* frontier to explore (estimated to be closest to the goal), mark it as visited, and return it
|
||
|
|
*/
|
||
|
4 years ago
|
uint16_t GetNextPath()
|
||
|
5 years ago
|
{
|
||
|
4 years ago
|
uint16_t result = Path2Nodes->nextNodeIndex;
|
||
|
|
if (result == PathNode::InvalidIndex) {
|
||
|
5 years ago
|
return result;
|
||
|
5 years ago
|
}
|
||
|
|
|
||
|
4 years ago
|
Path2Nodes->nextNodeIndex = PathNodes[result].nextNodeIndex;
|
||
|
|
PathNodes[result].nextNodeIndex = VisitedNodes->nextNodeIndex;
|
||
|
|
VisitedNodes->nextNodeIndex = result;
|
||
|
5 years ago
|
return result;
|
||
|
5 years ago
|
}
|
||
|
|
|
||
|
5 years ago
|
/** the number of in-use nodes in path_nodes */
|
||
|
5 years ago
|
uint32_t gdwCurNodes;
|
||
|
5 years ago
|
/**
|
||
|
5 years ago
|
* @brief zero one of the preallocated nodes and return a pointer to it, or NULL if none are available
|
||
|
5 years ago
|
*/
|
||
|
4 years ago
|
uint16_t NewStep()
|
||
|
5 years ago
|
{
|
||
|
4 years ago
|
if (gdwCurNodes >= MaxPathNodes)
|
||
|
|
return PathNode::InvalidIndex;
|
||
|
5 years ago
|
|
||
|
4 years ago
|
PathNodes[gdwCurNodes] = {};
|
||
|
|
return gdwCurNodes++;
|
||
|
5 years ago
|
}
|
||
|
|
|
||
|
5 years ago
|
/** A stack for recursively searching nodes */
|
||
|
4 years ago
|
uint16_t pnode_tblptr[MaxPathNodes];
|
||
|
5 years ago
|
/** size of the pnode_tblptr stack */
|
||
|
5 years ago
|
uint32_t gdwCurPathStep;
|
||
|
7 years ago
|
/**
|
||
|
5 years ago
|
* @brief push pPath onto the pnode_tblptr stack
|
||
|
8 years ago
|
*/
|
||
|
4 years ago
|
void PushActiveStep(uint16_t pPath)
|
||
|
8 years ago
|
{
|
||
|
4 years ago
|
assert(gdwCurPathStep < MaxPathNodes);
|
||
|
5 years ago
|
pnode_tblptr[gdwCurPathStep] = pPath;
|
||
|
|
gdwCurPathStep++;
|
||
|
8 years ago
|
}
|
||
|
|
|
||
|
7 years ago
|
/**
|
||
|
5 years ago
|
* @brief pop and return a node from the pnode_tblptr stack
|
||
|
7 years ago
|
*/
|
||
|
4 years ago
|
uint16_t PopActiveStep()
|
||
|
8 years ago
|
{
|
||
|
5 years ago
|
gdwCurPathStep--;
|
||
|
|
return pnode_tblptr[gdwCurPathStep];
|
||
|
8 years ago
|
}
|
||
|
|
|
||
|
7 years ago
|
/**
|
||
|
|
* @brief return 2 if pPath is horizontally/vertically aligned with (dx,dy), else 3
|
||
|
8 years ago
|
*
|
||
|
|
* This approximates that diagonal movement on a square grid should have a cost
|
||
|
|
* of sqrt(2). That's approximately 1.5, so they multiply all step costs by 2,
|
||
|
|
* except diagonal steps which are times 3
|
||
|
|
*/
|
||
|
5 years ago
|
int CheckEqual(Point startPosition, Point destinationPosition)
|
||
|
8 years ago
|
{
|
||
|
5 years ago
|
if (startPosition.x == destinationPosition.x || startPosition.y == destinationPosition.y)
|
||
|
8 years ago
|
return 2;
|
||
|
8 years ago
|
|
||
|
8 years ago
|
return 3;
|
||
|
8 years ago
|
}
|
||
|
|
|
||
|
7 years ago
|
/**
|
||
|
5 years ago
|
* @brief update all path costs using depth-first search starting at pPath
|
||
|
8 years ago
|
*/
|
||
|
4 years ago
|
void SetCoords(uint16_t pPath)
|
||
|
8 years ago
|
{
|
||
|
5 years ago
|
PushActiveStep(pPath);
|
||
|
5 years ago
|
// while there are path nodes to check
|
||
|
|
while (gdwCurPathStep > 0) {
|
||
|
4 years ago
|
uint16_t pathOldIndex = PopActiveStep();
|
||
|
|
const PathNode &pathOld = PathNodes[pathOldIndex];
|
||
|
|
for (uint16_t childIndex : pathOld.childIndices) {
|
||
|
|
if (childIndex == PathNode::InvalidIndex)
|
||
|
5 years ago
|
break;
|
||
|
4 years ago
|
PathNode &pathAct = PathNodes[childIndex];
|
||
|
|
|
||
|
|
if (pathOld.g + CheckEqual(pathOld.position(), pathAct.position()) < pathAct.g) {
|
||
|
|
if (path_solid_pieces(pathOld.position(), pathAct.position())) {
|
||
|
|
pathAct.parentIndex = pathOldIndex;
|
||
|
|
pathAct.g = pathOld.g + CheckEqual(pathOld.position(), pathAct.position());
|
||
|
|
pathAct.f = pathAct.g + pathAct.h;
|
||
|
|
PushActiveStep(childIndex);
|
||
|
5 years ago
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
8 years ago
|
}
|
||
|
|
}
|
||
|
|
|
||
|
7 years ago
|
/**
|
||
|
5 years ago
|
* Returns a number representing the direction from a starting tile to a neighbouring tile.
|
||
|
|
*
|
||
|
|
* Used in the pathfinding code, each step direction is assigned a number like this:
|
||
|
5 years ago
|
* dx
|
||
|
|
* -1 0 1
|
||
|
|
* +-----
|
||
|
|
* -1|5 1 6
|
||
|
|
* dy 0|2 0 3
|
||
|
|
* 1|8 4 7
|
||
|
8 years ago
|
*/
|
||
|
5 years ago
|
int8_t GetPathDirection(Point startPosition, Point destinationPosition)
|
||
|
8 years ago
|
{
|
||
|
5 years ago
|
constexpr int8_t PathDirections[9] = { 5, 1, 6, 2, 0, 3, 8, 4, 7 };
|
||
|
|
return PathDirections[3 * (destinationPosition.y - startPosition.y) + 4 + destinationPosition.x - startPosition.x];
|
||
|
8 years ago
|
}
|
||
|
|
|
||
|
5 years ago
|
/**
|
||
|
|
* @brief heuristic, estimated cost from startPosition to destinationPosition.
|
||
|
|
*/
|
||
|
5 years ago
|
int GetHeuristicCost(Point startPosition, Point destinationPosition)
|
||
|
5 years ago
|
{
|
||
|
|
// see path_check_equal for why this is times 2
|
||
|
|
return 2 * startPosition.ManhattanDistance(destinationPosition);
|
||
|
|
}
|
||
|
|
|
||
|
7 years ago
|
/**
|
||
|
5 years ago
|
* @brief add a step from pPath to destination, return 1 if successful, and update the frontier/visited nodes accordingly
|
||
|
8 years ago
|
*
|
||
|
4 years ago
|
* @param pathIndex index of the current path node
|
||
|
5 years ago
|
* @param candidatePosition expected to be a neighbour of the current path node position
|
||
|
|
* @param destinationPosition where we hope to end up
|
||
|
5 years ago
|
* @return true if step successfully added, false if we ran out of nodes to use
|
||
|
8 years ago
|
*/
|
||
|
4 years ago
|
bool ParentPath(uint16_t pathIndex, Point candidatePosition, Point destinationPosition)
|
||
|
8 years ago
|
{
|
||
|
4 years ago
|
PathNode &path = PathNodes[pathIndex];
|
||
|
|
int nextG = path.g + CheckEqual(path.position(), candidatePosition);
|
||
|
8 years ago
|
|
||
|
|
// 3 cases to consider
|
||
|
|
// case 1: (dx,dy) is already on the frontier
|
||
|
4 years ago
|
uint16_t dxdyIndex = GetNode1(candidatePosition);
|
||
|
|
if (dxdyIndex != PathNode::InvalidIndex) {
|
||
|
|
path.addChild(dxdyIndex);
|
||
|
|
PathNode &dxdy = PathNodes[dxdyIndex];
|
||
|
|
if (nextG < dxdy.g) {
|
||
|
|
if (path_solid_pieces(path.position(), candidatePosition)) {
|
||
|
8 years ago
|
// we'll explore it later, just update
|
||
|
4 years ago
|
dxdy.parentIndex = pathIndex;
|
||
|
|
dxdy.g = nextG;
|
||
|
|
dxdy.f = nextG + dxdy.h;
|
||
|
8 years ago
|
}
|
||
|
|
}
|
||
|
8 years ago
|
} else {
|
||
|
8 years ago
|
// case 2: (dx,dy) was already visited
|
||
|
4 years ago
|
dxdyIndex = GetNode2(candidatePosition);
|
||
|
|
if (dxdyIndex != PathNode::InvalidIndex) {
|
||
|
|
path.addChild(dxdyIndex);
|
||
|
|
PathNode &dxdy = PathNodes[dxdyIndex];
|
||
|
|
if (nextG < dxdy.g && path_solid_pieces(path.position(), candidatePosition)) {
|
||
|
8 years ago
|
// update the node
|
||
|
4 years ago
|
dxdy.parentIndex = pathIndex;
|
||
|
|
dxdy.g = nextG;
|
||
|
|
dxdy.f = nextG + dxdy.h;
|
||
|
8 years ago
|
// already explored, so re-update others starting from that node
|
||
|
4 years ago
|
SetCoords(dxdyIndex);
|
||
|
8 years ago
|
}
|
||
|
8 years ago
|
} else {
|
||
|
8 years ago
|
// case 3: (dx,dy) is totally new
|
||
|
4 years ago
|
dxdyIndex = NewStep();
|
||
|
|
if (dxdyIndex == PathNode::InvalidIndex)
|
||
|
5 years ago
|
return false;
|
||
|
4 years ago
|
PathNode &dxdy = PathNodes[dxdyIndex];
|
||
|
|
dxdy.parentIndex = pathIndex;
|
||
|
|
dxdy.g = nextG;
|
||
|
|
dxdy.h = GetHeuristicCost(candidatePosition, destinationPosition);
|
||
|
|
dxdy.f = nextG + dxdy.h;
|
||
|
|
dxdy.x = static_cast<int16_t>(candidatePosition.x);
|
||
|
|
dxdy.y = static_cast<int16_t>(candidatePosition.y);
|
||
|
8 years ago
|
// add it to the frontier
|
||
|
4 years ago
|
NextNode(dxdyIndex);
|
||
|
|
path.addChild(dxdyIndex);
|
||
|
8 years ago
|
}
|
||
|
|
}
|
||
|
5 years ago
|
return true;
|
||
|
8 years ago
|
}
|
||
|
|
|
||
|
7 years ago
|
/**
|
||
|
5 years ago
|
* @brief perform a single step of A* bread-first search by trying to step in every possible direction from pPath with goal (x,y). Check each step with PosOk
|
||
|
|
*
|
||
|
|
* @return false if we ran out of preallocated nodes to use, else true
|
||
|
7 years ago
|
*/
|
||
|
4 years ago
|
bool GetPath(const std::function<bool(Point)> &posOk, uint16_t pathIndex, Point destination)
|
||
|
8 years ago
|
{
|
||
|
4 years ago
|
for (Displacement dir : PathDirs) {
|
||
|
|
const PathNode &path = PathNodes[pathIndex];
|
||
|
|
const Point tile = path.position() + dir;
|
||
|
|
const bool ok = posOk(tile);
|
||
|
|
if ((ok && path_solid_pieces(path.position(), tile)) || (!ok && tile == destination)) {
|
||
|
|
if (!ParentPath(pathIndex, tile, destination))
|
||
|
5 years ago
|
return false;
|
||
|
|
}
|
||
|
6 years ago
|
}
|
||
|
5 years ago
|
|
||
|
|
return true;
|
||
|
8 years ago
|
}
|
||
|
|
|
||
|
5 years ago
|
} // namespace
|
||
|
5 years ago
|
|
||
|
|
bool IsTileNotSolid(Point position)
|
||
|
8 years ago
|
{
|
||
|
4 years ago
|
if (!InDungeonBounds(position)) {
|
||
|
5 years ago
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
4 years ago
|
return !TileHasAny(dPiece[position.x][position.y], TileProperties::Solid);
|
||
|
8 years ago
|
}
|
||
|
|
|
||
|
5 years ago
|
bool IsTileSolid(Point position)
|
||
|
8 years ago
|
{
|
||
|
4 years ago
|
if (!InDungeonBounds(position)) {
|
||
|
5 years ago
|
return false;
|
||
|
5 years ago
|
}
|
||
|
|
|
||
|
4 years ago
|
return TileHasAny(dPiece[position.x][position.y], TileProperties::Solid);
|
||
|
5 years ago
|
}
|
||
|
|
|
||
|
|
bool IsTileWalkable(Point position, bool ignoreDoors)
|
||
|
|
{
|
||
|
4 years ago
|
Object *object = ObjectAtPosition(position);
|
||
|
|
if (object != nullptr) {
|
||
|
|
if (ignoreDoors && object->IsDoor()) {
|
||
|
5 years ago
|
return true;
|
||
|
4 years ago
|
}
|
||
|
|
if (object->_oSolidFlag) {
|
||
|
5 years ago
|
return false;
|
||
|
4 years ago
|
}
|
||
|
8 years ago
|
}
|
||
|
5 years ago
|
|
||
|
|
return !IsTileSolid(position);
|
||
|
8 years ago
|
}
|
||
|
|
|
||
|
4 years ago
|
bool IsTileOccupied(Point position)
|
||
|
|
{
|
||
|
|
if (!InDungeonBounds(position)) {
|
||
|
|
return true; // OOB positions are considered occupied.
|
||
|
|
}
|
||
|
|
|
||
|
|
if (IsTileSolid(position)) {
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
if (dMonster[position.x][position.y] != 0) {
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
if (dPlayer[position.x][position.y] != 0) {
|
||
|
|
return true;
|
||
|
|
}
|
||
|
4 years ago
|
if (IsObjectAtPosition(position)) {
|
||
|
4 years ago
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
4 years ago
|
int FindPath(const std::function<bool(Point)> &posOk, Point startPosition, Point destinationPosition, int8_t path[MaxPathLength])
|
||
|
8 years ago
|
{
|
||
|
5 years ago
|
/**
|
||
|
|
* for reconstructing the path after the A* search is done. The longest
|
||
|
|
* possible path is actually 24 steps, even though we can fit 25
|
||
|
|
*/
|
||
|
4 years ago
|
static int8_t pnodeVals[MaxPathLength];
|
||
|
8 years ago
|
|
||
|
5 years ago
|
// clear all nodes, create root nodes for the visited/frontier linked lists
|
||
|
|
gdwCurNodes = 0;
|
||
|
4 years ago
|
Path2Nodes = &PathNodes[NewStep()];
|
||
|
|
VisitedNodes = &PathNodes[NewStep()];
|
||
|
5 years ago
|
gdwCurPathStep = 0;
|
||
|
4 years ago
|
const uint16_t pathStartIndex = NewStep();
|
||
|
|
PathNode &pathStart = PathNodes[pathStartIndex];
|
||
|
|
pathStart.x = static_cast<int16_t>(startPosition.x);
|
||
|
|
pathStart.y = static_cast<int16_t>(startPosition.y);
|
||
|
|
pathStart.f = pathStart.h + pathStart.g;
|
||
|
|
pathStart.h = GetHeuristicCost(startPosition, destinationPosition);
|
||
|
|
pathStart.g = 0;
|
||
|
|
Path2Nodes->nextNodeIndex = pathStartIndex;
|
||
|
5 years ago
|
// A* search until we find (dx,dy) or fail
|
||
|
4 years ago
|
uint16_t nextNodeIndex;
|
||
|
|
while ((nextNodeIndex = GetNextPath()) != PathNode::InvalidIndex) {
|
||
|
5 years ago
|
// reached the end, success!
|
||
|
4 years ago
|
if (PathNodes[nextNodeIndex].position() == destinationPosition) {
|
||
|
|
const PathNode *current = &PathNodes[nextNodeIndex];
|
||
|
|
size_t pathLength = 0;
|
||
|
|
while (current->parentIndex != PathNode::InvalidIndex) {
|
||
|
|
if (pathLength >= MaxPathLength)
|
||
|
5 years ago
|
break;
|
||
|
4 years ago
|
pnodeVals[pathLength++] = GetPathDirection(PathNodes[current->parentIndex].position(), current->position());
|
||
|
|
current = &PathNodes[current->parentIndex];
|
||
|
5 years ago
|
}
|
||
|
4 years ago
|
if (pathLength != MaxPathLength) {
|
||
|
|
size_t i;
|
||
|
5 years ago
|
for (i = 0; i < pathLength; i++)
|
||
|
5 years ago
|
path[i] = pnodeVals[pathLength - i - 1];
|
||
|
4 years ago
|
return static_cast<int>(i);
|
||
|
8 years ago
|
}
|
||
|
5 years ago
|
return 0;
|
||
|
8 years ago
|
}
|
||
|
5 years ago
|
// ran out of nodes, abort!
|
||
|
4 years ago
|
if (!GetPath(posOk, nextNodeIndex, destinationPosition))
|
||
|
5 years ago
|
return 0;
|
||
|
8 years ago
|
}
|
||
|
5 years ago
|
// frontier is empty, no path!
|
||
|
|
return 0;
|
||
|
8 years ago
|
}
|
||
|
|
|
||
|
5 years ago
|
bool path_solid_pieces(Point startPosition, Point destinationPosition)
|
||
|
8 years ago
|
{
|
||
|
5 years ago
|
// These checks are written as if working backwards from the destination to the source, given
|
||
|
|
// both tiles are expected to be adjacent this doesn't matter beyond being a bit confusing
|
||
|
|
bool rv = true;
|
||
|
5 years ago
|
switch (GetPathDirection(startPosition, destinationPosition)) {
|
||
|
5 years ago
|
case 5: // Stepping north
|
||
|
5 years ago
|
rv = IsTileNotSolid(destinationPosition + Direction::SouthWest) && IsTileNotSolid(destinationPosition + Direction::SouthEast);
|
||
|
5 years ago
|
break;
|
||
|
|
case 6: // Stepping east
|
||
|
5 years ago
|
rv = IsTileNotSolid(destinationPosition + Direction::SouthWest) && IsTileNotSolid(destinationPosition + Direction::NorthWest);
|
||
|
5 years ago
|
break;
|
||
|
|
case 7: // Stepping south
|
||
|
5 years ago
|
rv = IsTileNotSolid(destinationPosition + Direction::NorthEast) && IsTileNotSolid(destinationPosition + Direction::NorthWest);
|
||
|
5 years ago
|
break;
|
||
|
|
case 8: // Stepping west
|
||
|
5 years ago
|
rv = IsTileNotSolid(destinationPosition + Direction::SouthEast) && IsTileNotSolid(destinationPosition + Direction::NorthEast);
|
||
|
5 years ago
|
break;
|
||
|
|
}
|
||
|
|
return rv;
|
||
|
8 years ago
|
}
|
||
|
7 years ago
|
|
||
|
4 years ago
|
std::optional<Point> FindClosestValidPosition(const std::function<bool(Point)> &posOk, Point startingPosition, unsigned int minimumRadius, unsigned int maximumRadius)
|
||
|
|
{
|
||
|
|
if (minimumRadius > maximumRadius) {
|
||
|
|
return {}; // No valid search space with the given params.
|
||
|
|
}
|
||
|
|
|
||
|
|
if (minimumRadius == 0U) {
|
||
|
|
if (posOk(startingPosition)) {
|
||
|
|
return startingPosition;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (minimumRadius <= 1U && maximumRadius >= 1U) {
|
||
|
|
// unrolling the case for radius 1 to save having to guard the corner check in the loop below.
|
||
|
|
|
||
|
|
Point candidatePosition = startingPosition + Direction::SouthWest;
|
||
|
|
if (posOk(candidatePosition)) {
|
||
|
|
return candidatePosition;
|
||
|
|
}
|
||
|
|
candidatePosition = startingPosition + Direction::NorthEast;
|
||
|
|
if (posOk(candidatePosition)) {
|
||
|
|
return candidatePosition;
|
||
|
|
}
|
||
|
|
|
||
|
|
candidatePosition = startingPosition + Direction::NorthWest;
|
||
|
|
if (posOk(candidatePosition)) {
|
||
|
|
return candidatePosition;
|
||
|
|
}
|
||
|
|
|
||
|
|
candidatePosition = startingPosition + Direction::SouthEast;
|
||
|
|
if (posOk(candidatePosition)) {
|
||
|
|
return candidatePosition;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (maximumRadius >= 2U) {
|
||
|
|
for (int i = static_cast<int>(std::max(minimumRadius, 2U)); i <= static_cast<int>(std::min(maximumRadius, 50U)); i++) {
|
||
|
|
int x = 0;
|
||
|
|
int y = i;
|
||
|
|
|
||
|
|
// special case the checks when x == 0 to save checking the same tiles twice
|
||
|
|
Point candidatePosition = startingPosition + Displacement { x, y };
|
||
|
|
if (posOk(candidatePosition)) {
|
||
|
|
return candidatePosition;
|
||
|
|
}
|
||
|
|
candidatePosition = startingPosition + Displacement { x, -y };
|
||
|
|
if (posOk(candidatePosition)) {
|
||
|
|
return candidatePosition;
|
||
|
|
}
|
||
|
|
|
||
|
|
while (x < i - 1) {
|
||
|
|
x++;
|
||
|
|
|
||
|
|
candidatePosition = startingPosition + Displacement { -x, y };
|
||
|
|
if (posOk(candidatePosition)) {
|
||
|
|
return candidatePosition;
|
||
|
|
}
|
||
|
|
|
||
|
|
candidatePosition = startingPosition + Displacement { x, y };
|
||
|
|
if (posOk(candidatePosition)) {
|
||
|
|
return candidatePosition;
|
||
|
|
}
|
||
|
|
|
||
|
|
candidatePosition = startingPosition + Displacement { -x, -y };
|
||
|
|
if (posOk(candidatePosition)) {
|
||
|
|
return candidatePosition;
|
||
|
|
}
|
||
|
|
|
||
|
|
candidatePosition = startingPosition + Displacement { x, -y };
|
||
|
|
if (posOk(candidatePosition)) {
|
||
|
|
return candidatePosition;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// special case for inset corners
|
||
|
|
y--;
|
||
|
|
candidatePosition = startingPosition + Displacement { -x, y };
|
||
|
|
if (posOk(candidatePosition)) {
|
||
|
|
return candidatePosition;
|
||
|
|
}
|
||
|
|
|
||
|
|
candidatePosition = startingPosition + Displacement { x, y };
|
||
|
|
if (posOk(candidatePosition)) {
|
||
|
|
return candidatePosition;
|
||
|
|
}
|
||
|
|
|
||
|
|
candidatePosition = startingPosition + Displacement { -x, -y };
|
||
|
|
if (posOk(candidatePosition)) {
|
||
|
|
return candidatePosition;
|
||
|
|
}
|
||
|
|
|
||
|
|
candidatePosition = startingPosition + Displacement { x, -y };
|
||
|
|
if (posOk(candidatePosition)) {
|
||
|
|
return candidatePosition;
|
||
|
|
}
|
||
|
|
x++;
|
||
|
|
|
||
|
|
while (y > 0) {
|
||
|
|
candidatePosition = startingPosition + Displacement { -x, y };
|
||
|
|
if (posOk(candidatePosition)) {
|
||
|
|
return candidatePosition;
|
||
|
|
}
|
||
|
|
|
||
|
|
candidatePosition = startingPosition + Displacement { x, y };
|
||
|
|
if (posOk(candidatePosition)) {
|
||
|
|
return candidatePosition;
|
||
|
|
}
|
||
|
|
|
||
|
|
candidatePosition = startingPosition + Displacement { -x, -y };
|
||
|
|
if (posOk(candidatePosition)) {
|
||
|
|
return candidatePosition;
|
||
|
|
}
|
||
|
|
|
||
|
|
candidatePosition = startingPosition + Displacement { x, -y };
|
||
|
|
if (posOk(candidatePosition)) {
|
||
|
|
return candidatePosition;
|
||
|
|
}
|
||
|
|
|
||
|
|
y--;
|
||
|
|
}
|
||
|
|
|
||
|
|
// as above, special case for y == 0
|
||
|
|
candidatePosition = startingPosition + Displacement { -x, y };
|
||
|
|
if (posOk(candidatePosition)) {
|
||
|
|
return candidatePosition;
|
||
|
|
}
|
||
|
|
|
||
|
|
candidatePosition = startingPosition + Displacement { x, y };
|
||
|
|
if (posOk(candidatePosition)) {
|
||
|
|
return candidatePosition;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return {};
|
||
|
|
}
|
||
|
|
|
||
|
4 years ago
|
#ifdef BUILD_TESTING
|
||
|
5 years ago
|
int TestPathGetHeuristicCost(Point startPosition, Point destinationPosition)
|
||
|
|
{
|
||
|
5 years ago
|
return GetHeuristicCost(startPosition, destinationPosition);
|
||
|
5 years ago
|
}
|
||
|
|
#endif
|
||
|
|
|
||
|
5 years ago
|
} // namespace devilution
|