2013-03-31 17:30:03 +00:00
|
|
|
#include "pathfinding.hpp"
|
2016-06-17 14:07:16 +00:00
|
|
|
|
2015-08-21 10:41:31 +00:00
|
|
|
#include <limits>
|
2013-05-29 22:59:23 +00:00
|
|
|
|
2014-02-23 19:11:05 +00:00
|
|
|
#include "../mwbase/world.hpp"
|
|
|
|
#include "../mwbase/environment.hpp"
|
2013-05-29 22:59:23 +00:00
|
|
|
|
2014-02-23 19:11:05 +00:00
|
|
|
#include "../mwworld/cellstore.hpp"
|
2016-06-17 14:07:16 +00:00
|
|
|
|
2017-11-27 17:30:31 +00:00
|
|
|
#include "pathgrid.hpp"
|
2015-08-16 06:55:02 +00:00
|
|
|
#include "coordinateconverter.hpp"
|
2013-03-31 17:30:03 +00:00
|
|
|
|
2013-04-11 17:02:12 +00:00
|
|
|
namespace
|
2013-03-31 17:30:03 +00:00
|
|
|
{
|
2014-04-03 10:41:34 +00:00
|
|
|
// Chooses a reachable end pathgrid point. start is assumed reachable.
|
2014-03-29 08:28:30 +00:00
|
|
|
std::pair<int, bool> getClosestReachablePoint(const ESM::Pathgrid* grid,
|
2017-11-27 17:30:31 +00:00
|
|
|
const MWMechanics::PathgridGraph *graph,
|
2017-04-20 11:36:14 +00:00
|
|
|
const osg::Vec3f& pos, int start)
|
2013-05-30 00:33:33 +00:00
|
|
|
{
|
2015-08-09 05:58:40 +00:00
|
|
|
assert(grid && !grid->mPoints.empty());
|
2014-01-26 20:26:19 +00:00
|
|
|
|
2015-08-21 07:34:28 +00:00
|
|
|
float closestDistanceBetween = std::numeric_limits<float>::max();
|
|
|
|
float closestDistanceReachable = std::numeric_limits<float>::max();
|
2014-03-29 08:28:30 +00:00
|
|
|
int closestIndex = 0;
|
|
|
|
int closestReachableIndex = 0;
|
|
|
|
// TODO: if this full scan causes performance problems mapping pathgrid
|
|
|
|
// points to a quadtree may help
|
2015-08-20 18:55:54 +00:00
|
|
|
for(unsigned int counter = 0; counter < grid->mPoints.size(); counter++)
|
2013-05-30 00:33:33 +00:00
|
|
|
{
|
2016-04-11 01:44:08 +00:00
|
|
|
float potentialDistBetween = MWMechanics::PathFinder::DistanceSquared(grid->mPoints[counter], pos);
|
2015-08-20 09:50:58 +00:00
|
|
|
if (potentialDistBetween < closestDistanceReachable)
|
2013-05-30 00:33:33 +00:00
|
|
|
{
|
2014-03-29 08:28:30 +00:00
|
|
|
// found a closer one
|
2017-11-27 17:30:31 +00:00
|
|
|
if (graph->isPointConnected(start, counter))
|
2014-01-26 20:26:19 +00:00
|
|
|
{
|
2015-08-20 09:50:58 +00:00
|
|
|
closestDistanceReachable = potentialDistBetween;
|
2014-03-29 08:28:30 +00:00
|
|
|
closestReachableIndex = counter;
|
2014-01-26 20:26:19 +00:00
|
|
|
}
|
2015-08-20 09:50:58 +00:00
|
|
|
if (potentialDistBetween < closestDistanceBetween)
|
|
|
|
{
|
|
|
|
closestDistanceBetween = potentialDistBetween;
|
|
|
|
closestIndex = counter;
|
|
|
|
}
|
2013-03-31 17:30:03 +00:00
|
|
|
}
|
|
|
|
}
|
2015-08-20 09:50:58 +00:00
|
|
|
|
2015-08-21 07:34:28 +00:00
|
|
|
// post-condition: start and endpoint must be connected
|
2017-11-27 17:30:31 +00:00
|
|
|
assert(graph->isPointConnected(start, closestReachableIndex));
|
2015-08-20 09:50:58 +00:00
|
|
|
|
2014-04-18 04:25:08 +00:00
|
|
|
// AiWander has logic that depends on whether a path was created, deleting
|
|
|
|
// allowed nodes if not. Hence a path needs to be created even if the start
|
|
|
|
// and the end points are the same.
|
2014-03-29 08:28:30 +00:00
|
|
|
|
|
|
|
return std::pair<int, bool>
|
|
|
|
(closestReachableIndex, closestReachableIndex == closestIndex);
|
|
|
|
}
|
2013-05-30 00:33:33 +00:00
|
|
|
|
2018-07-21 09:30:14 +00:00
|
|
|
float sqrDistanceIgnoreZ(const osg::Vec3f& point, float x, float y)
|
2014-05-13 01:05:32 +00:00
|
|
|
{
|
2018-07-21 09:30:14 +00:00
|
|
|
x -= point.x();
|
|
|
|
y -= point.y();
|
2015-03-23 07:09:46 +00:00
|
|
|
return (x * x + y * y);
|
2014-05-13 01:05:32 +00:00
|
|
|
}
|
2018-07-21 09:30:14 +00:00
|
|
|
}
|
2014-05-13 01:05:32 +00:00
|
|
|
|
2018-07-21 09:30:14 +00:00
|
|
|
namespace MWMechanics
|
|
|
|
{
|
2016-08-19 19:15:26 +00:00
|
|
|
bool checkWayIsClear(const osg::Vec3f& from, const osg::Vec3f& to, float offsetXY)
|
|
|
|
{
|
|
|
|
osg::Vec3f dir = to - from;
|
|
|
|
dir.z() = 0;
|
|
|
|
dir.normalize();
|
|
|
|
float verticalOffset = 200; // instead of '200' here we want the height of the actor
|
|
|
|
osg::Vec3f _from = from + dir*offsetXY + osg::Z_AXIS * verticalOffset;
|
|
|
|
|
|
|
|
// cast up-down ray and find height of hit in world space
|
|
|
|
float h = _from.z() - MWBase::Environment::get().getWorld()->getDistToNearestRayHit(_from, -osg::Z_AXIS, verticalOffset + PATHFIND_Z_REACH + 1);
|
|
|
|
|
|
|
|
return (std::abs(from.z() - h) <= PATHFIND_Z_REACH);
|
|
|
|
}
|
|
|
|
|
2013-03-31 17:30:03 +00:00
|
|
|
PathFinder::PathFinder()
|
2018-10-09 06:21:12 +00:00
|
|
|
: mPathgrid(nullptr)
|
|
|
|
, mCell(nullptr)
|
2013-03-31 17:30:03 +00:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2013-05-29 22:59:23 +00:00
|
|
|
void PathFinder::clearPath()
|
|
|
|
{
|
|
|
|
if(!mPath.empty())
|
|
|
|
mPath.clear();
|
|
|
|
}
|
|
|
|
|
2014-03-26 21:37:05 +00:00
|
|
|
/*
|
2014-03-29 08:28:30 +00:00
|
|
|
* NOTE: This method may fail to find a path. The caller must check the
|
|
|
|
* result before using it. If there is no path the AI routies need to
|
|
|
|
* implement some other heuristics to reach the target.
|
|
|
|
*
|
2014-04-03 10:41:34 +00:00
|
|
|
* NOTE: It may be desirable to simply go directly to the endPoint if for
|
|
|
|
* example there are no pathgrids in this cell.
|
|
|
|
*
|
2016-12-14 21:11:22 +00:00
|
|
|
* NOTE: startPoint & endPoint are in world coordinates
|
2014-03-26 21:37:05 +00:00
|
|
|
*
|
2014-03-29 08:28:30 +00:00
|
|
|
* Updates mPath using aStarSearch() or ray test (if shortcut allowed).
|
|
|
|
* mPath consists of pathgrid points, except the last element which is
|
|
|
|
* endPoint. This may be useful where the endPoint is not on a pathgrid
|
|
|
|
* point (e.g. combat). However, if the caller has already chosen a
|
|
|
|
* pathgrid point (e.g. wander) then it may be worth while to call
|
|
|
|
* pop_back() to remove the redundant entry.
|
2014-03-26 21:37:05 +00:00
|
|
|
*
|
2016-12-14 21:11:22 +00:00
|
|
|
* NOTE: coordinates must be converted prior to calling GetClosestPoint()
|
2014-03-29 08:28:30 +00:00
|
|
|
*
|
|
|
|
* |
|
|
|
|
* | cell
|
|
|
|
* | +-----------+
|
|
|
|
* | | |
|
|
|
|
* | | |
|
|
|
|
* | | @ |
|
|
|
|
* | i | j |
|
|
|
|
* |<--->|<---->| |
|
|
|
|
* | +-----------+
|
|
|
|
* | k
|
|
|
|
* |<---------->| world
|
|
|
|
* +-----------------------------
|
|
|
|
*
|
|
|
|
* i = x value of cell itself (multiply by ESM::Land::REAL_SIZE to convert)
|
2016-12-14 21:11:22 +00:00
|
|
|
* j = @.x in local coordinates (i.e. within the cell)
|
|
|
|
* k = @.x in world coordinates
|
2014-03-26 21:37:05 +00:00
|
|
|
*/
|
2018-07-21 09:30:14 +00:00
|
|
|
void PathFinder::buildPath(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint,
|
2017-11-27 17:30:31 +00:00
|
|
|
const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph)
|
2014-01-26 20:53:55 +00:00
|
|
|
{
|
2014-01-11 10:57:07 +00:00
|
|
|
mPath.clear();
|
2014-01-26 20:53:55 +00:00
|
|
|
|
2017-11-27 17:30:31 +00:00
|
|
|
// TODO: consider removing mCell / mPathgrid in favor of mPathgridGraph
|
2014-04-03 10:41:34 +00:00
|
|
|
if(mCell != cell || !mPathgrid)
|
2014-01-26 20:53:55 +00:00
|
|
|
{
|
2014-03-29 08:28:30 +00:00
|
|
|
mCell = cell;
|
2017-11-27 17:30:31 +00:00
|
|
|
mPathgrid = pathgridGraph.getPathgrid();
|
2014-01-26 20:53:55 +00:00
|
|
|
}
|
2014-01-26 21:06:54 +00:00
|
|
|
|
2014-04-03 10:41:34 +00:00
|
|
|
// Refer to AiWander reseach topic on openmw forums for some background.
|
|
|
|
// Maybe there is no pathgrid for this cell. Just go to destination and let
|
|
|
|
// physics take care of any blockages.
|
|
|
|
if(!mPathgrid || mPathgrid->mPoints.empty())
|
2014-01-26 21:06:54 +00:00
|
|
|
{
|
2014-04-03 10:41:34 +00:00
|
|
|
mPath.push_back(endPoint);
|
|
|
|
return;
|
2014-01-26 21:06:54 +00:00
|
|
|
}
|
|
|
|
|
2016-12-14 21:11:22 +00:00
|
|
|
// NOTE: GetClosestPoint expects local coordinates
|
2015-08-16 06:55:02 +00:00
|
|
|
CoordinateConverter converter(mCell->getCell());
|
2013-06-01 00:01:42 +00:00
|
|
|
|
2016-04-11 01:44:08 +00:00
|
|
|
// NOTE: It is possible that GetClosestPoint returns a pathgrind point index
|
2014-03-29 08:28:30 +00:00
|
|
|
// that is unreachable in some situations. e.g. actor is standing
|
|
|
|
// outside an area enclosed by walls, but there is a pathgrid
|
|
|
|
// point right behind the wall that is closer than any pathgrid
|
|
|
|
// point outside the wall
|
2015-09-12 02:17:46 +00:00
|
|
|
osg::Vec3f startPointInLocalCoords(converter.toLocalVec3(startPoint));
|
2016-04-11 01:44:08 +00:00
|
|
|
int startNode = GetClosestPoint(mPathgrid, startPointInLocalCoords);
|
2015-08-09 05:58:40 +00:00
|
|
|
|
2015-09-12 02:17:46 +00:00
|
|
|
osg::Vec3f endPointInLocalCoords(converter.toLocalVec3(endPoint));
|
2017-11-27 17:30:31 +00:00
|
|
|
std::pair<int, bool> endNode = getClosestReachablePoint(mPathgrid, &pathgridGraph,
|
2015-08-09 05:58:40 +00:00
|
|
|
endPointInLocalCoords,
|
|
|
|
startNode);
|
|
|
|
|
2015-09-19 03:34:02 +00:00
|
|
|
// if it's shorter for actor to travel from start to end, than to travel from either
|
|
|
|
// start or end to nearest pathgrid point, just travel from start to end.
|
|
|
|
float startToEndLength2 = (endPointInLocalCoords - startPointInLocalCoords).length2();
|
2016-04-11 01:44:08 +00:00
|
|
|
float endTolastNodeLength2 = DistanceSquared(mPathgrid->mPoints[endNode.first], endPointInLocalCoords);
|
|
|
|
float startTo1stNodeLength2 = DistanceSquared(mPathgrid->mPoints[startNode], startPointInLocalCoords);
|
2015-09-19 03:34:02 +00:00
|
|
|
if ((startToEndLength2 < startTo1stNodeLength2) || (startToEndLength2 < endTolastNodeLength2))
|
2013-03-31 17:30:03 +00:00
|
|
|
{
|
2015-09-19 03:34:02 +00:00
|
|
|
mPath.push_back(endPoint);
|
|
|
|
return;
|
|
|
|
}
|
2014-01-12 17:42:31 +00:00
|
|
|
|
2015-08-09 05:58:40 +00:00
|
|
|
// AiWander has logic that depends on whether a path was created,
|
|
|
|
// deleting allowed nodes if not. Hence a path needs to be created
|
|
|
|
// even if the start and the end points are the same.
|
|
|
|
// NOTE: aStarSearch will return an empty path if the start and end
|
|
|
|
// nodes are the same
|
|
|
|
if(startNode == endNode.first)
|
2013-03-31 17:30:03 +00:00
|
|
|
{
|
2015-08-16 06:56:28 +00:00
|
|
|
ESM::Pathgrid::Point temp(mPathgrid->mPoints[startNode]);
|
2015-09-12 02:17:46 +00:00
|
|
|
converter.toWorld(temp);
|
2018-07-21 09:30:14 +00:00
|
|
|
mPath.push_back(MakeOsgVec3(temp));
|
2015-08-09 05:58:40 +00:00
|
|
|
}
|
2015-08-30 04:12:51 +00:00
|
|
|
else
|
2015-08-16 06:55:02 +00:00
|
|
|
{
|
2018-07-21 09:30:14 +00:00
|
|
|
auto path = pathgridGraph.aStarSearch(startNode, endNode.first);
|
2013-06-01 00:01:42 +00:00
|
|
|
|
2018-07-29 13:12:36 +00:00
|
|
|
// If nearest path node is in opposite direction from second, remove it from path.
|
|
|
|
// Especially useful for wandering actors, if the nearest node is blocked for some reason.
|
2018-07-21 09:30:14 +00:00
|
|
|
if (path.size() > 1)
|
2018-07-29 13:12:36 +00:00
|
|
|
{
|
2018-07-21 09:30:14 +00:00
|
|
|
ESM::Pathgrid::Point secondNode = *(++path.begin());
|
2018-07-29 13:12:36 +00:00
|
|
|
osg::Vec3f firstNodeVec3f = MakeOsgVec3(mPathgrid->mPoints[startNode]);
|
|
|
|
osg::Vec3f secondNodeVec3f = MakeOsgVec3(secondNode);
|
|
|
|
osg::Vec3f toSecondNodeVec3f = secondNodeVec3f - firstNodeVec3f;
|
|
|
|
osg::Vec3f toStartPointVec3f = startPointInLocalCoords - firstNodeVec3f;
|
|
|
|
if (toSecondNodeVec3f * toStartPointVec3f > 0)
|
|
|
|
{
|
|
|
|
ESM::Pathgrid::Point temp(secondNode);
|
|
|
|
converter.toWorld(temp);
|
|
|
|
// Add Z offset since path node can overlap with other objects.
|
|
|
|
// Also ignore doors in raytesting.
|
|
|
|
bool isPathClear = !MWBase::Environment::get().getWorld()->castRay(
|
2018-07-21 09:30:14 +00:00
|
|
|
startPoint.x(), startPoint.y(), startPoint.z() + 16, temp.mX, temp.mY, temp.mZ + 16, true);
|
2018-07-29 13:12:36 +00:00
|
|
|
if (isPathClear)
|
2018-07-21 09:30:14 +00:00
|
|
|
path.pop_front();
|
2018-07-29 13:12:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-12-14 21:11:22 +00:00
|
|
|
// convert supplied path to world coordinates
|
2018-07-21 09:30:14 +00:00
|
|
|
std::transform(path.begin(), path.end(), std::back_inserter(mPath),
|
|
|
|
[&] (ESM::Pathgrid::Point& point)
|
|
|
|
{
|
|
|
|
converter.toWorld(point);
|
|
|
|
return MakeOsgVec3(point);
|
|
|
|
});
|
2013-05-05 23:46:50 +00:00
|
|
|
}
|
2014-04-03 10:41:34 +00:00
|
|
|
|
2015-08-16 05:41:33 +00:00
|
|
|
// If endNode found is NOT the closest PathGrid point to the endPoint,
|
2018-07-21 09:30:14 +00:00
|
|
|
// assume endPoint is not reachable from endNode. In which case,
|
2015-08-16 05:41:33 +00:00
|
|
|
// path ends at endNode.
|
|
|
|
//
|
|
|
|
// So only add the destination (which may be different to the closest
|
|
|
|
// pathgrid point) when endNode was the closest point to endPoint.
|
|
|
|
//
|
|
|
|
// This logic can fail in the opposite situate, e.g. endPoint may
|
|
|
|
// have been reachable but happened to be very close to an
|
|
|
|
// unreachable pathgrid point.
|
|
|
|
//
|
|
|
|
// The AI routines will have to deal with such situations.
|
|
|
|
if(endNode.second)
|
|
|
|
mPath.push_back(endPoint);
|
2013-03-31 17:30:03 +00:00
|
|
|
}
|
|
|
|
|
2013-08-30 02:17:27 +00:00
|
|
|
float PathFinder::getZAngleToNext(float x, float y) const
|
2013-03-31 17:30:03 +00:00
|
|
|
{
|
2014-03-26 21:37:05 +00:00
|
|
|
// This should never happen (programmers should have an if statement checking
|
2015-06-14 03:14:02 +00:00
|
|
|
// isPathConstructed that prevents this call if otherwise).
|
2013-03-31 17:30:03 +00:00
|
|
|
if(mPath.empty())
|
2013-10-07 08:20:02 +00:00
|
|
|
return 0.;
|
2013-05-29 22:59:23 +00:00
|
|
|
|
2018-07-21 09:30:14 +00:00
|
|
|
const auto& nextPoint = mPath.front();
|
|
|
|
const float directionX = nextPoint.x() - x;
|
|
|
|
const float directionY = nextPoint.y() - y;
|
2013-05-30 00:33:33 +00:00
|
|
|
|
2015-07-19 06:01:25 +00:00
|
|
|
return std::atan2(directionX, directionY);
|
2013-03-31 17:30:03 +00:00
|
|
|
}
|
|
|
|
|
2016-08-19 19:15:26 +00:00
|
|
|
float PathFinder::getXAngleToNext(float x, float y, float z) const
|
|
|
|
{
|
|
|
|
// This should never happen (programmers should have an if statement checking
|
|
|
|
// isPathConstructed that prevents this call if otherwise).
|
|
|
|
if(mPath.empty())
|
|
|
|
return 0.;
|
|
|
|
|
2018-07-21 09:30:14 +00:00
|
|
|
const osg::Vec3f dir = mPath.front() - osg::Vec3f(x, y, z);
|
2016-08-19 19:15:26 +00:00
|
|
|
|
2016-09-03 14:41:03 +00:00
|
|
|
return getXAngleToDir(dir);
|
2016-08-19 19:15:26 +00:00
|
|
|
}
|
|
|
|
|
2015-03-23 07:09:46 +00:00
|
|
|
bool PathFinder::checkPathCompleted(float x, float y, float tolerance)
|
2013-03-31 17:30:03 +00:00
|
|
|
{
|
|
|
|
if(mPath.empty())
|
|
|
|
return true;
|
2013-05-29 22:59:23 +00:00
|
|
|
|
2018-07-21 09:30:14 +00:00
|
|
|
if (sqrDistanceIgnoreZ(mPath.front(), x, y) < tolerance*tolerance)
|
2013-03-31 17:30:03 +00:00
|
|
|
{
|
|
|
|
mPath.pop_front();
|
|
|
|
if(mPath.empty())
|
2013-05-30 03:05:17 +00:00
|
|
|
{
|
2013-03-31 17:30:03 +00:00
|
|
|
return true;
|
2013-05-30 03:05:17 +00:00
|
|
|
}
|
2013-03-31 17:30:03 +00:00
|
|
|
}
|
2013-05-30 00:33:33 +00:00
|
|
|
|
2013-03-31 17:30:03 +00:00
|
|
|
return false;
|
|
|
|
}
|
2014-01-19 20:09:51 +00:00
|
|
|
|
2015-06-11 06:31:35 +00:00
|
|
|
// see header for the rationale
|
2018-07-21 09:30:14 +00:00
|
|
|
void PathFinder::buildSyncedPath(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint,
|
2017-11-27 17:30:31 +00:00
|
|
|
const MWWorld::CellStore* cell, const MWMechanics::PathgridGraph& pathgridGraph)
|
2014-01-19 20:09:51 +00:00
|
|
|
{
|
2014-02-24 20:41:10 +00:00
|
|
|
if (mPath.size() < 2)
|
2014-01-19 20:09:51 +00:00
|
|
|
{
|
2015-06-11 06:31:35 +00:00
|
|
|
// if path has one point, then it's the destination.
|
|
|
|
// don't need to worry about bad path for this case
|
2017-11-27 17:30:31 +00:00
|
|
|
buildPath(startPoint, endPoint, cell, pathgridGraph);
|
2015-06-11 06:31:35 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2018-07-21 09:30:14 +00:00
|
|
|
const auto oldStart = getPath().front();
|
2017-11-27 17:30:31 +00:00
|
|
|
buildPath(startPoint, endPoint, cell, pathgridGraph);
|
2018-04-17 09:11:05 +00:00
|
|
|
if (mPath.size() >= 2)
|
2015-06-11 06:31:35 +00:00
|
|
|
{
|
2018-04-17 09:11:05 +00:00
|
|
|
// if 2nd waypoint of new path == 1st waypoint of old,
|
|
|
|
// delete 1st waypoint of new path.
|
2018-07-21 09:30:14 +00:00
|
|
|
const auto iter = ++mPath.begin();
|
|
|
|
if (*iter == oldStart)
|
2018-04-17 09:11:05 +00:00
|
|
|
{
|
|
|
|
mPath.pop_front();
|
|
|
|
}
|
2015-06-11 06:31:35 +00:00
|
|
|
}
|
2014-01-19 20:09:51 +00:00
|
|
|
}
|
|
|
|
}
|
2014-01-27 20:38:01 +00:00
|
|
|
|
2016-09-05 22:11:10 +00:00
|
|
|
const MWWorld::CellStore* PathFinder::getPathCell() const
|
|
|
|
{
|
|
|
|
return mCell;
|
|
|
|
}
|
2013-05-29 22:59:23 +00:00
|
|
|
}
|