/* 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 . * */ /* * This code is based on the CRAB engine * * Copyright (c) Arvind Raja Yadav * * Licensed under MIT * */ #include "common/system.h" #include "crab/crab.h" #include "crab/PathfindingAgent.h" namespace Crab { // This keeps the PriorityQueue organized based on the cost of the paths. static bool compareNodes(PlannerNode const *nodeA, PlannerNode const *nodeB) { return nodeA->getFinalCost() > nodeB->getFinalCost(); } PathfindingAgent::PathfindingAgent() : _nodeQueue(compareNodes) { _grid = nullptr; _destinationSet = false; _destinationReachable = false; _nodeBufferDistance = 1.0f; _solutionFound = _noSolution = false; _startTile = nullptr; _goalTile = nullptr; _clickedTile = nullptr; } PathfindingAgent::~PathfindingAgent() { } void PathfindingAgent::initialize(PathfindingGrid *g) { _grid = g; _nodeBufferDistance = _grid->getCellSize().x / 2.0f; _nodeBufferDistance *= _nodeBufferDistance; } void PathfindingAgent::setDestination(Vector2i d, bool r) { Vector2f iVec = Vector2f((float)d.x, (float)d.y); setDestination(iVec, r); } void PathfindingAgent::setDestination(Vector2i d) { setDestination(d, true); } void PathfindingAgent::setDestination(Vector2f d) { setDestination(d, true); } void PathfindingAgent::setDestination(Vector2f d, bool r) { if (_grid == nullptr) return; _destination = d; // TODO: This could be optimized to cache the route somehow... (SZ) reset(); _startTile = _grid->getNodeAtPoint(_position); // m_pGoalTile = grid->GetNodeAtPoint(d); // I am now tracking the goal node and the clicked tile separately to solve problems // with hangups and trying to reach un-reachable destinations. _clickedTile = _grid->getNodeAtPoint(d); _goalTile = _grid->getNearestOpenNode(d, _position); PlannerNode *startingNode = new PlannerNode(); startingNode->setLocation(_startTile); startingNode->setHCost((_position - _destination).magnitude()); startingNode->setFinalCost((_position - _destination).magnitude()); startingNode->setGivenCost(0.0); _nodeQueue.push(startingNode); _createdList[_startTile] = (startingNode); _destinationSet = true; _solutionFound = _noSolution = false; _destinationReachable = r; } void PathfindingAgent::update(uint32 timeslice) { uint32 prevTime = g_system->getMillis(); uint32 timeLeft = timeslice; double dTempCost; if (_solutionFound) { if (_vSolution.size() > 0) { float distSqr = (_position - _vSolution.back()->getPosition()).magSqr(); if (distSqr < _nodeBufferDistance) { // Have to find the right deadzone buffer _vSolution.pop_back(); } } if (_vSolution.size() > 0) { _immediateDest = Vector2i(_vSolution.back()->getPosition().x, _vSolution.back()->getPosition().y); } else { if (_destinationReachable) _immediateDest = Vector2i((int)_destination.x, (int)_destination.y); else _immediateDest = Vector2i((int)_position.x, (int)_position.y); } return; } // No nodes, no pathing. if (_nodeQueue.empty()) { return; } Common::StableMap::iterator currentIter; do { PlannerNode *current = _nodeQueue.front(); _nodeQueue.pop(); if (current->getLocation() == _goalTile) { // We're done. // m_vSolution = getSolution(); _vSolution = getPrunedSolution(nullptr); _solutionFound = true; return; } else if (current->getLocation()->getMovementCost() > 0 && current->getLocation()->adjacentToNode(_clickedTile) && _clickedTile->getMovementCost() < 0) { _vSolution = getPrunedSolution(current->getLocation()); _solutionFound = true; return; } for (auto &i : current->_location->_neighborNodes) { if (i->getMovementCost() > 0) { // Compute the temp given cost dTempCost = current->getGivenCost() + i->getMovementCost() * distExact(i, current->getLocation()); // If it's a duplicate... currentIter = _createdList.find(i); if (currentIter != _createdList.end()) { if (dTempCost < currentIter->second->getGivenCost()) { // If the current planner node has already been added, but the current path is cheaper, // replace it. _nodeQueue.remove(currentIter->second); currentIter->second->setGivenCost(dTempCost); currentIter->second->setFinalCost( currentIter->second->getHCost() * 1.1 + currentIter->second->getGivenCost()); currentIter->second->setParent(current); _nodeQueue.push(currentIter->second); } } else { // Otherwise... PlannerNode *successor = new PlannerNode(); successor->setLocation(i); // Set the new heuristic (distance from node to the goal) successor->setHCost(distExact(i, _goalTile)); successor->setGivenCost(dTempCost); // Final cost is the distance to goal (scaled by 10%) plus the distance of the path. successor->setFinalCost(successor->getHCost() * 1.1 + successor->getGivenCost()); successor->setParent(current); _createdList[i] = (successor); _nodeQueue.push(successor); // When the node is pushed onto the PriorityQueue it ends up beings sorted cheapest -> most expensive } } } // Update the time if (timeslice != 0) { timeLeft -= (g_system->getMillis() - prevTime); prevTime = g_system->getMillis(); } } while (!isDone() && ((int32)timeLeft >= 0 || timeslice == 0)); _noSolution = true; // You can't get there from here (SZ) } bool PathfindingAgent::isDone() const { if (_nodeQueue.empty()) return true; return false; } // Clear everything. void PathfindingAgent::reset() { for (auto &iter : _createdList) delete iter.second; _nodeQueue.clear(); _createdList.clear(); _vSolution.clear(); _solutionFound = false; _goalTile = nullptr; _startTile = nullptr; } void PathfindingAgent::shutdown() { reset(); _grid = nullptr; } Common::Array const PathfindingAgent::getSolution(PathfindingGraphNode *destNode) const { Common::Array temp; PlannerNode *current = nullptr; if (_createdList.find(_goalTile) != _createdList.end()) { current = _createdList.find(_goalTile)->second; } else if (destNode != nullptr) { // If the dest node passed in is not null, that means we did not reach the goal but came close // so we should start with that node instead when we are constructing our path current = _createdList.find(destNode)->second; } // Iterate through the planner nodes to create a vector to return. while (current) { if (current->getLocation() != _startTile) { // You don't have to path to the start if (current->getLocation() != _startTile) temp.push_back(current->getLocation()); } current = current->getParent(); } return temp; } Common::Array const PathfindingAgent::getPrunedSolution(PathfindingGraphNode *destNode) { Common::Array temp = getSolution(destNode); Common::Array returnVec = temp; // Any node that is not adjacent to an obstacle or an obstacle corner can be removed. for (int i = 0; (uint)i < temp.size(); ++i) { if (!temp[i]->adjacentToObstacle()) { if (i > 0 && (uint)i < temp.size() - 1) { // This check to see if the node is a "corner" to an obstacle that should not be pruned // to prevent hanging on corners. Common::Array corners = _grid->cornerCheck(temp[i - 1], temp[i + 1]); if (corners.size() == 0) { Common::Array::iterator theEnd = Common::remove(returnVec.begin(), returnVec.end(), temp[i]); returnVec.erase(theEnd); } } } } return returnVec; } double PathfindingAgent::distSquared(PathfindingGraphNode *tileA, PathfindingGraphNode *tileB) { Vector2f vecTo = tileA->getPosition() - tileB->getPosition(); return vecTo.magSqr(); } double PathfindingAgent::distExact(PathfindingGraphNode *tileA, PathfindingGraphNode *tileB) { Vector2f vecTo = tileA->getPosition() - tileB->getPosition(); return vecTo.magnitude(); } bool PathfindingAgent::adjacentToGoal(PathfindingGraphNode *node) { for (const auto &iter : node->_neighborNodes) { if (iter == _goalTile) { return true; } } return false; } } // End of namespace Crab