|
|
|
#pragma region License
|
|
|
|
/*
|
|
|
|
License (OLC-3)
|
|
|
|
~~~~~~~~~~~~~~~
|
|
|
|
|
|
|
|
Copyright 2024 Joshua Sigona <sigonasr2@gmail.com>
|
|
|
|
|
|
|
|
Redistribution and use in source and binary forms, with or without modification,
|
|
|
|
are permitted provided that the following conditions are met:
|
|
|
|
|
|
|
|
1. Redistributions or derivations of source code must retain the above copyright
|
|
|
|
notice, this list of conditions and the following disclaimer.
|
|
|
|
|
|
|
|
2. Redistributions or derivative works in binary form must reproduce the above
|
|
|
|
copyright notice. This list of conditions and the following disclaimer must be
|
|
|
|
reproduced in the documentation and/or other materials provided with the distribution.
|
|
|
|
|
|
|
|
3. Neither the name of the copyright holder nor the names of its contributors may
|
|
|
|
be used to endorse or promote products derived from this software without specific
|
|
|
|
prior written permission.
|
|
|
|
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
|
|
|
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
|
|
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
|
|
|
|
SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
|
|
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
|
|
|
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
|
|
|
|
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
|
|
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
|
|
|
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
|
|
SUCH DAMAGE.
|
|
|
|
|
|
|
|
Portions of this software are copyright © 2023 The FreeType
|
|
|
|
Project (www.freetype.org). Please see LICENSE_FT.txt for more information.
|
|
|
|
All rights reserved.
|
|
|
|
*/
|
|
|
|
#pragma endregion
|
|
|
|
#include "Pathfinding.h"
|
|
|
|
#include "DEFINES.h"
|
|
|
|
#include "AdventuresInLestoria.h"
|
|
|
|
|
|
|
|
INCLUDE_game
|
|
|
|
|
|
|
|
void Pathfinding::Initialize(){
|
|
|
|
if(nodes!=nullptr){
|
|
|
|
delete[] nodes;
|
|
|
|
}
|
|
|
|
nodes = NEW sNode[game->GetCurrentMapData().width * game->GetCurrentMapData().height];
|
|
|
|
for (int x = 0; x < game->GetCurrentMapData().width; x++)
|
|
|
|
for (int y = 0; y < game->GetCurrentMapData().height; y++)
|
|
|
|
{
|
|
|
|
nodes[y * game->GetCurrentMapData().width + x].x = x; // ...because we give each node its own coordinates
|
|
|
|
nodes[y * game->GetCurrentMapData().width + x].y = y;
|
|
|
|
geom2d::rect<int>tile=game->GetTileCollision(game->GetCurrentLevel(),{float(x*game->GetCurrentMapData().tilewidth),float(y*game->GetCurrentMapData().tilewidth)});
|
|
|
|
nodes[y * game->GetCurrentMapData().width + x].bObstacle = tile.pos!=game->NO_COLLISION.pos||tile.size!=game->NO_COLLISION.size;
|
|
|
|
tile=game->GetTileCollision(game->GetCurrentLevel(),{float(x*game->GetCurrentMapData().tilewidth),float(y*game->GetCurrentMapData().tilewidth)},true);
|
|
|
|
nodes[y * game->GetCurrentMapData().width + x].bObstacleUpper = tile.pos!=game->NO_COLLISION.pos||tile.size!=game->NO_COLLISION.size;
|
|
|
|
nodes[y * game->GetCurrentMapData().width + x].parent = nullptr;
|
|
|
|
nodes[y * game->GetCurrentMapData().width + x].bVisited = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (int x = 0; x < game->GetCurrentMapData().width; x++)
|
|
|
|
for (int y = 0; y < game->GetCurrentMapData().height; y++)
|
|
|
|
{
|
|
|
|
if(y>0)
|
|
|
|
nodes[y*game->GetCurrentMapData().width + x].vecNeighbours.push_back(&nodes[(y - 1) * game->GetCurrentMapData().width + (x + 0)]);
|
|
|
|
if(y<game->GetCurrentMapData().height-1)
|
|
|
|
nodes[y*game->GetCurrentMapData().width + x].vecNeighbours.push_back(&nodes[(y + 1) * game->GetCurrentMapData().width + (x + 0)]);
|
|
|
|
if (x>0)
|
|
|
|
nodes[y*game->GetCurrentMapData().width + x].vecNeighbours.push_back(&nodes[(y + 0) * game->GetCurrentMapData().width + (x - 1)]);
|
|
|
|
if(x<game->GetCurrentMapData().width-1)
|
|
|
|
nodes[y*game->GetCurrentMapData().width + x].vecNeighbours.push_back(&nodes[(y + 0) * game->GetCurrentMapData().width + (x + 1)]);
|
|
|
|
if (y>0 && x>0)
|
|
|
|
nodes[y*game->GetCurrentMapData().width + x].vecNeighbours.push_back(&nodes[(y - 1) * game->GetCurrentMapData().width + (x - 1)]);
|
|
|
|
if (y<game->GetCurrentMapData().height-1 && x>0)
|
|
|
|
nodes[y*game->GetCurrentMapData().width + x].vecNeighbours.push_back(&nodes[(y + 1) * game->GetCurrentMapData().width + (x - 1)]);
|
|
|
|
if (y>0 && x<game->GetCurrentMapData().width-1)
|
|
|
|
nodes[y*game->GetCurrentMapData().width + x].vecNeighbours.push_back(&nodes[(y - 1) * game->GetCurrentMapData().width + (x + 1)]);
|
|
|
|
if (y<game->GetCurrentMapData().height - 1 && x<game->GetCurrentMapData().width-1)
|
|
|
|
nodes[y*game->GetCurrentMapData().width + x].vecNeighbours.push_back(&nodes[(y + 1) * game->GetCurrentMapData().width + (x + 1)]);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Manually position the start and end markers so they are not nullptr
|
|
|
|
nodeStart = &nodes[(game->GetCurrentMapData().height / 2) * game->GetCurrentMapData().width + 1];
|
|
|
|
nodeEnd = &nodes[(game->GetCurrentMapData().height / 2) * game->GetCurrentMapData().width + game->GetCurrentMapData().width-2];
|
|
|
|
}
|
|
|
|
|
|
|
|
std::vector<vf2d> Pathfinding::Solve_AStar(vf2d startPos,vf2d endPos,float maxRange,bool upperLevel){
|
|
|
|
float dist=float(sqrt(pow(endPos.x-startPos.x,2)+pow(endPos.y-startPos.y,2)));
|
|
|
|
if(dist>maxRange*game->GetCurrentMapData().tilewidth)return {};
|
|
|
|
|
|
|
|
nodeStart=&nodes[int(startPos.y/game->GetCurrentMapData().tilewidth)*game->GetCurrentMapData().width+int(startPos.x/game->GetCurrentMapData().tilewidth)];
|
|
|
|
nodeEnd=&nodes[int(endPos.y/game->GetCurrentMapData().tilewidth)*game->GetCurrentMapData().width+int(endPos.x/game->GetCurrentMapData().tilewidth)];
|
|
|
|
|
|
|
|
|
|
|
|
geom2d::rect<int>posPerimeter{{int(std::min(startPos.x,endPos.x)),int(std::min(startPos.y,endPos.y))},{int(abs(endPos.x-startPos.x)),int(abs(endPos.y-startPos.y))}};
|
|
|
|
posPerimeter.pos={int(std::clamp(posPerimeter.pos.x-maxRange*game->GetCurrentMapData().tilewidth,0.f,game->GetCurrentMapData().width*float(game->GetCurrentMapData().tilewidth))),int(std::clamp(posPerimeter.pos.y-maxRange*game->GetCurrentMapData().tilewidth,0.f,game->GetCurrentMapData().height*float(game->GetCurrentMapData().tilewidth)))};
|
|
|
|
posPerimeter.size={int(std::clamp(posPerimeter.size.x+maxRange*game->GetCurrentMapData().tilewidth*2,0.f,game->GetCurrentMapData().width*float(game->GetCurrentMapData().tilewidth)-posPerimeter.pos.x)),int(std::clamp(posPerimeter.size.y+maxRange*game->GetCurrentMapData().tilewidth*2,0.f,game->GetCurrentMapData().height*float(game->GetCurrentMapData().tilewidth)-posPerimeter.pos.y))};
|
|
|
|
|
|
|
|
for (int x = 0; x < game->GetCurrentMapData().width; x++){
|
|
|
|
for (int y = 0; y < game->GetCurrentMapData().height; y++){
|
|
|
|
if(geom2d::overlaps(posPerimeter,vi2d{x*game->GetCurrentMapData().tilewidth,y*game->GetCurrentMapData().tilewidth})){
|
|
|
|
nodes[y*game->GetCurrentMapData().width + x].bVisited = false;
|
|
|
|
} else {
|
|
|
|
nodes[y*game->GetCurrentMapData().width + x].bVisited = true;
|
|
|
|
}
|
|
|
|
nodes[y*game->GetCurrentMapData().width + x].fGlobalGoal = INFINITY;
|
|
|
|
nodes[y*game->GetCurrentMapData().width + x].fLocalGoal = INFINITY;
|
|
|
|
nodes[y*game->GetCurrentMapData().width + x].parent = nullptr; // No parents
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
auto distance = [](sNode* a, sNode* b) // For convenience
|
|
|
|
{
|
|
|
|
return sqrtf(float((a->x - b->x)*(a->x - b->x) + (a->y - b->y)*(a->y - b->y)));
|
|
|
|
};
|
|
|
|
|
|
|
|
auto heuristic = [distance](sNode* a, sNode* b)
|
|
|
|
{
|
|
|
|
return distance(a, b);
|
|
|
|
};
|
|
|
|
|
|
|
|
sNode *nodeCurrent = nodeStart;
|
|
|
|
nodeStart->fLocalGoal = 0.0f;
|
|
|
|
nodeStart->fGlobalGoal = heuristic(nodeStart, nodeEnd);
|
|
|
|
|
|
|
|
std::list<sNode*> listNotTestedNodes;
|
|
|
|
//if((!upperLevel && nodeStart->bObstacle)||(upperLevel && nodeStart->bObstacleUpper))return {};
|
|
|
|
listNotTestedNodes.push_back(nodeStart);
|
|
|
|
|
|
|
|
while (!listNotTestedNodes.empty() && nodeCurrent != nodeEnd)
|
|
|
|
{
|
|
|
|
listNotTestedNodes.sort([](const sNode* lhs, const sNode* rhs){ return lhs->fGlobalGoal < rhs->fGlobalGoal; } );
|
|
|
|
|
|
|
|
while(!listNotTestedNodes.empty() && listNotTestedNodes.front()->bVisited)
|
|
|
|
listNotTestedNodes.pop_front();
|
|
|
|
if (listNotTestedNodes.empty())
|
|
|
|
break;
|
|
|
|
|
|
|
|
nodeCurrent = listNotTestedNodes.front();
|
|
|
|
nodeCurrent->bVisited = true;
|
|
|
|
for (auto nodeNeighbour : nodeCurrent->vecNeighbours)
|
|
|
|
{
|
|
|
|
if (!nodeNeighbour->bVisited && ((!upperLevel && nodeNeighbour->bObstacle == 0)||(upperLevel && nodeNeighbour->bObstacleUpper==0)))
|
|
|
|
listNotTestedNodes.push_back(nodeNeighbour);
|
|
|
|
|
|
|
|
float fPossiblyLowerGoal = nodeCurrent->fLocalGoal + distance(nodeCurrent, nodeNeighbour);
|
|
|
|
|
|
|
|
if (fPossiblyLowerGoal < nodeNeighbour->fLocalGoal)
|
|
|
|
{
|
|
|
|
nodeNeighbour->parent = nodeCurrent;
|
|
|
|
nodeNeighbour->fLocalGoal = fPossiblyLowerGoal;
|
|
|
|
nodeNeighbour->fGlobalGoal = nodeNeighbour->fLocalGoal + heuristic(nodeNeighbour, nodeEnd);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (nodeEnd != nullptr)
|
|
|
|
{
|
|
|
|
int pathLength=INFINITE;
|
|
|
|
sNode *p = nodeEnd;
|
|
|
|
std::vector<vf2d>finalPath;
|
|
|
|
while (p->parent != nullptr)
|
|
|
|
{
|
|
|
|
if(pathLength==INFINITE){
|
|
|
|
pathLength=1;
|
|
|
|
} else {
|
|
|
|
pathLength++;
|
|
|
|
}
|
|
|
|
finalPath.push_back({float((*p).x),float((*p).y)});
|
|
|
|
p = p->parent;
|
|
|
|
}
|
|
|
|
std::reverse(finalPath.begin(),finalPath.end());
|
|
|
|
return finalPath;
|
|
|
|
} else {
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
}
|