Files
scummvm-cursorfix/engines/hpl1/engine/ai/AStar.cpp
2026-02-02 04:50:13 +01:00

324 lines
9.7 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/*
* Copyright (C) 2006-2010 - Frictional Games
*
* This file is part of HPL1 Engine.
*/
#include "hpl1/engine/ai/AStar.h"
#include "hpl1/engine/ai/AINodeContainer.h"
#include "hpl1/engine/math/Math.h"
#include "hpl1/engine/system/low_level_system.h"
namespace hpl {
//////////////////////////////////////////////////////////////////////////
// NODE
//////////////////////////////////////////////////////////////////////////
//-----------------------------------------------------------------------
cAStarNode::cAStarNode(cAINode *apAINode) {
mpParent = NULL;
mpAINode = apAINode;
}
//-----------------------------------------------------------------------
bool cAStarNodeCompare::operator()(cAStarNode *apNodeA, cAStarNode *apNodeB) const {
return apNodeA->mpAINode < apNodeB->mpAINode;
}
//-----------------------------------------------------------------------
//////////////////////////////////////////////////////////////////////////
// CONSTRUCTORS
//////////////////////////////////////////////////////////////////////////
//-----------------------------------------------------------------------
cAStarHandler::cAStarHandler(cAINodeContainer *apContainer) {
mlMaxIterations = -1;
mpContainer = apContainer;
mpCallback = NULL;
}
//-----------------------------------------------------------------------
cAStarHandler::~cAStarHandler() {
STLDeleteAll(m_setClosedList);
STLDeleteAll(m_setOpenList);
}
//-----------------------------------------------------------------------
//////////////////////////////////////////////////////////////////////////
// PUBLIC METHODS
//////////////////////////////////////////////////////////////////////////
//-----------------------------------------------------------------------
bool cAStarHandler::GetPath(const cVector3f &avStart, const cVector3f &avGoal, tAINodeList *apNodeList) {
/////////////////////////////////////////////////
// check if there is free path from start to goal
if (mpContainer->FreePath(avStart, avGoal, 3)) {
mpGoalNode = NULL;
return true;
}
////////////////////////////////////////////////
// Reset all variables
// These should just be cleared and pools used instead.
STLDeleteAll(m_setClosedList);
STLDeleteAll(m_setOpenList);
m_setGoalNodes.clear();
mpGoalNode = NULL;
// Set goal position
mvGoal = avGoal;
float fMaxHeight = mpContainer->GetMaxHeight() * 1.5f;
////////////////////////////////////////////////
// Find nodes reachable from the start and goal position (use double 2*2 distance)
float fMaxDist = mpContainer->GetMaxEdgeDistance() * 2; // float fMaxDist = mpContainer->GetMaxEdgeDistance()*mpContainer->GetMaxEdgeDistance()*4;
/////////////////////
// Check with Start
// Log(" Get Start\n");
cAINodeIterator startNodeIt = mpContainer->GetNodeIterator(avStart, fMaxDist);
while (startNodeIt.HasNext()) {
cAINode *pAINode = startNodeIt.Next();
// Log("Check node: %s\n",pAINode->GetName().c_str());
float fHeight = fabs(avStart.y - pAINode->GetPosition().y);
float fDist = cMath::Vector3Dist(avStart, pAINode->GetPosition()); // float fDist = cMath::Vector3DistSqr(avStart,pAINode->GetPosition());
if (fDist < fMaxDist && fHeight <= fMaxHeight) {
// Check if path is clear
if (mpContainer->FreePath(avStart, pAINode->GetPosition(), -1,
eAIFreePathFlag_SkipDynamic)) {
AddOpenNode(pAINode, NULL, fDist);
}
}
}
// Log(" Found start\n");
////////////////////////////////
// Check with Goal
// Log(" Get Goal\n");
cAINodeIterator goalNodeIt = mpContainer->GetNodeIterator(avGoal, fMaxDist);
while (goalNodeIt.HasNext()) {
cAINode *pAINode = goalNodeIt.Next();
// Log("Check node: %s\n",pAINode->GetName().c_str());
float fHeight = fabs(avGoal.y - pAINode->GetPosition().y);
float fDist = cMath::Vector3Dist(avGoal, pAINode->GetPosition()); // fDist = cMath::Vector3DistSqr(avGoal,pAINode->GetPosition());
if (fDist < fMaxDist && fHeight <= fMaxHeight) {
// Check if path is clear
if (mpContainer->FreePath(avGoal, pAINode->GetPosition(), 3)) {
m_setGoalNodes.insert(pAINode);
}
}
}
// Log(" Found goal\n");
/*for(int i=0; i<mpContainer->GetNodeNum(); ++i)
{
cAINode *pAINode = mpContainer->GetNode(i);
////////////////////////////////
//Check with Start
float fHeight = fabs(avStart.y - pAINode->GetPosition().y);
float fDist = cMath::Vector3Dist(avStart,pAINode->GetPosition());
//float fDist = cMath::Vector3DistSqr(avStart,pAINode->GetPosition());
if(fDist < fMaxDist && fHeight <= fMaxHeight)
{
//Check if path is clear
if(mpContainer->FreePath(avStart,pAINode->GetPosition(),-1))
{
AddOpenNode(pAINode,NULL,fDist);
}
}
////////////////////////////////
//Check with Goal
fHeight = fabs(avGoal.y - pAINode->GetPosition().y);
fDist = cMath::Vector3Dist(avGoal,pAINode->GetPosition());
//fDist = cMath::Vector3DistSqr(avGoal,pAINode->GetPosition());
if(fDist < fMaxDist && fHeight <= fMaxHeight)
{
//Check if path is clear
if(mpContainer->FreePath(avGoal,pAINode->GetPosition(),3))
{
m_setGoalNodes.insert(pAINode);
}
}
}*/
////////////////////////////////////////////////
// Iterate the algorithm
IterateAlgorithm();
////////////////////////////////////////////////
// Check if goal was found, if so build path.
if (mpGoalNode) {
if (apNodeList) {
cAStarNode *pParentNode = mpGoalNode;
while (pParentNode != NULL) {
apNodeList->push_back(pParentNode->mpAINode);
pParentNode = pParentNode->mpParent;
}
}
return true;
} else {
return false;
}
}
//-----------------------------------------------------------------------
//////////////////////////////////////////////////////////////////////////
// PRIVATE METHODS
//////////////////////////////////////////////////////////////////////////
//-----------------------------------------------------------------------
void cAStarHandler::IterateAlgorithm() {
int lIterationCount = 0;
while (m_setOpenList.empty() == false && (mlMaxIterations < 0 || lIterationCount < mlMaxIterations)) {
cAStarNode *pNode = GetBestNode();
cAINode *pAINode = pNode->mpAINode;
//////////////////////
// Check if current node can reach goal
if (IsGoalNode(pAINode)) {
mpGoalNode = pNode;
break;
}
/////////////////////
// Add nodes connected to current
int lEdgeCount = pAINode->GetEdgeNum();
for (int i = 0; i < lEdgeCount; ++i) {
cAINodeEdge *pEdge = pAINode->GetEdge(i);
if (mpCallback == NULL || mpCallback->CanAddNode(pAINode, pEdge->mpNode)) {
AddOpenNode(pEdge->mpNode, pNode, pNode->mfDistance + pEdge->mfDistance);
// AddOpenNode(pEdge->mpNode, pNode, pNode->mfDistance + pEdge->mfSqrDistance);
}
}
++lIterationCount;
}
}
//-----------------------------------------------------------------------
void cAStarHandler::AddOpenNode(cAINode *apAINode, cAStarNode *apParent, float afDistance) {
// TODO: free path check with dynamic objects here.
// TODO: Some pooling here would be good.
cAStarNode *pNode = hplNew(cAStarNode, (apAINode));
// Check if it is in closed list.
tAStarNodeSetIt it = m_setClosedList.find(pNode);
if (it != m_setClosedList.end()) {
hplDelete(pNode);
return;
}
// Add it if it wasn't already inserted
const auto test = m_setOpenList.find(pNode);
if (test != m_setOpenList.end()) {
hplDelete(pNode);
return;
}
m_setOpenList.insert(pNode);
pNode->mfDistance = afDistance;
pNode->mfCost = Cost(afDistance, apAINode, apParent) + Heuristic(pNode->mpAINode->GetPosition(), mvGoal);
pNode->mpParent = apParent;
}
//-----------------------------------------------------------------------
cAStarNode *cAStarHandler::GetBestNode() {
tAStarNodeSetIt it = m_setOpenList.begin();
tAStarNodeSetIt bestIt = it;
cAStarNode *pBestNode = *it;
++it;
// Iterate open list and find the best node.
for (; it != m_setOpenList.end(); ++it) {
cAStarNode *pNode = *it;
if (pBestNode->mfCost > pNode->mfCost) {
pBestNode = pNode;
bestIt = it;
}
}
// Remove node from open
m_setOpenList.erase(bestIt);
// Add to closed list
m_setClosedList.insert(pBestNode);
return pBestNode;
}
//-----------------------------------------------------------------------
float cAStarHandler::Cost(float afDistance, cAINode *apAINode, cAStarNode *apParent) {
if (apParent) {
float fHeight = (1 + fabs(apAINode->GetPosition().y - apParent->mpAINode->GetPosition().y));
return afDistance * fHeight;
} else
return afDistance;
}
//-----------------------------------------------------------------------
float cAStarHandler::Heuristic(const cVector3f &avStart, const cVector3f &avGoal) {
// return cMath::Vector3DistSqr(avStart, avGoal);
return cMath::Vector3Dist(avStart, avGoal);
}
//-----------------------------------------------------------------------
bool cAStarHandler::IsGoalNode(cAINode *apAINode) {
tAINodeSetIt it = m_setGoalNodes.find(apAINode);
if (it == m_setGoalNodes.end())
return false;
return true;
}
//-----------------------------------------------------------------------
} // namespace hpl