move checkWayIsClear to pathfinding; move shortcut logic to separate func (AiPackage::shortcutPath); rework AiPackage::pathTo

This commit is contained in:
mrcheko 2015-07-04 18:00:16 +03:00
parent c644f15167
commit c773ed9f9a
9 changed files with 248 additions and 103 deletions

View file

@ -34,20 +34,17 @@ bool MWMechanics::AiActivate::execute (const MWWorld::Ptr& actor, AiState& state
) )
return true; //Target doesn't exist return true; //Target doesn't exist
//Set the target desition from the actor //Set the target destination for the actor
ESM::Pathgrid::Point dest = target.getRefData().getPosition().pos; ESM::Pathgrid::Point dest = target.getRefData().getPosition().pos;
if(distance(dest, pos.pos[0], pos.pos[1], pos.pos[2]) < 200) { //Stop when you get close if (pathTo(actor, dest, duration, 200)) //Go to the destination
actor.getClass().getMovementSettings(actor).mPosition[1] = 0; {
// activate when reached
MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getPtr(mObjectId,false); MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getPtr(mObjectId,false);
MWBase::Environment::get().getWorld()->activate(target, actor); MWBase::Environment::get().getWorld()->activate(target, actor);
return true; return true;
} }
else {
pathTo(actor, dest, duration); //Go to the destination
}
return false; return false;
} }

View file

@ -37,46 +37,6 @@ namespace
Ogre::Vector3 AimDirToMovingTarget(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, const Ogre::Vector3& vLastTargetPos, Ogre::Vector3 AimDirToMovingTarget(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, const Ogre::Vector3& vLastTargetPos,
float duration, int weapType, float strength); float duration, int weapType, float strength);
float getZAngleToDir(const Ogre::Vector3& dir)
{
return Ogre::Math::ATan2(dir.x,dir.y).valueDegrees();
}
float getXAngleToDir(const Ogre::Vector3& dir, float dirLen = 0.0f)
{
float len = (dirLen > 0.0f)? dirLen : dir.length();
return -Ogre::Math::ASin(dir.z / len).valueDegrees();
}
const float PATHFIND_Z_REACH = 50.0f;
// distance at which actor pays more attention to decide whether to shortcut or stick to pathgrid
const float PATHFIND_CAUTION_DIST = 500.0f;
// distance after which actor (failed previously to shortcut) will try again
const float PATHFIND_SHORTCUT_RETRY_DIST = 300.0f;
// cast up-down ray with some offset from actor position to check for pits/obstacles on the way to target;
// magnitude of pits/obstacles is defined by PATHFIND_Z_REACH
bool checkWayIsClear(const Ogre::Vector3& from, const Ogre::Vector3& to, float offsetXY)
{
if((to - from).length() >= PATHFIND_CAUTION_DIST || std::abs(from.z - to.z) <= PATHFIND_Z_REACH)
{
Ogre::Vector3 dir = to - from;
dir.z = 0;
dir.normalise();
float verticalOffset = 200; // instead of '200' here we want the height of the actor
Ogre::Vector3 _from = from + dir*offsetXY + Ogre::Vector3::UNIT_Z * verticalOffset;
// cast up-down ray and find height in world space of hit
float h = _from.z - MWBase::Environment::get().getWorld()->getDistToNearestRayHit(_from, -Ogre::Vector3::UNIT_Z, verticalOffset + PATHFIND_Z_REACH + 1);
if(std::abs(from.z - h) <= PATHFIND_Z_REACH)
return true;
}
return false;
}
} }
namespace MWMechanics namespace MWMechanics
@ -702,7 +662,7 @@ namespace MWMechanics
if (doesPathNeedRecalc(newPathTarget, actor.getCell()->getCell())) if (doesPathNeedRecalc(newPathTarget, actor.getCell()->getCell()))
{ {
ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(actor.getRefData().getPosition())); ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(actor.getRefData().getPosition()));
mPathFinder.buildSyncedPath(start, newPathTarget, actor.getCell(), false); mPathFinder.buildSyncedPath(start, newPathTarget, actor.getCell());
} }
} }

View file

@ -130,19 +130,7 @@ bool AiFollow::execute (const MWWorld::Ptr& actor, AiState& state, float duratio
//Set the target destination from the actor //Set the target destination from the actor
ESM::Pathgrid::Point dest = target.getRefData().getPosition().pos; ESM::Pathgrid::Point dest = target.getRefData().getPosition().pos;
if(distance(dest, pos.pos[0], pos.pos[1], pos.pos[2]) < followDistance) //Stop when you get close pathTo(actor, dest, duration, followDistance); //Go to the destination
{
actor.getClass().getMovementSettings(actor).mPosition[1] = 0;
// turn towards target anyway
float directionX = target.getRefData().getPosition().pos[0] - actor.getRefData().getPosition().pos[0];
float directionY = target.getRefData().getPosition().pos[1] - actor.getRefData().getPosition().pos[1];
zTurn(actor, Ogre::Math::ATan2(directionX,directionY), Ogre::Degree(5));
}
else
{
pathTo(actor, dest, duration); //Go to the destination
}
//Check if you're far away //Check if you're far away
if(distance(dest, pos.pos[0], pos.pos[1], pos.pos[2]) > 450) if(distance(dest, pos.pos[0], pos.pos[1], pos.pos[2]) > 450)

View file

@ -16,12 +16,14 @@
MWMechanics::AiPackage::~AiPackage() {} MWMechanics::AiPackage::~AiPackage() {}
MWMechanics::AiPackage::AiPackage() : mTimer(0.26f), mStuckTimer(0) { //mTimer starts at .26 to force initial pathbuild MWMechanics::AiPackage::AiPackage() :
mTimer(0.26f), mStuckTimer(0), //mTimer starts at .26 to force initial pathbuild
mShortcutProhibited(false), mShortcutFailPos()
{
} }
bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, ESM::Pathgrid::Point dest, float duration) bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const ESM::Pathgrid::Point& dest, float duration, float destTolerance)
{ {
//Update various Timers //Update various Timers
mTimer += duration; //Update timer mTimer += duration; //Update timer
@ -63,13 +65,51 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, ESM::Pathgrid::Po
//*********************** //***********************
/// Checks if you can't get to the end position at all, adds end position to end of path /// Checks if you can't get to the end position at all, adds end position to end of path
/// Rebuilds path every quarter of a second, in case the target has moved /// Rebuilds path every [AI_REACTION_TIME] seconds, in case the target has moved
//*********************** //***********************
if(mTimer > 0.25)
bool isStuck = distance(start, mStuckPos.pos[0], mStuckPos.pos[1], mStuckPos.pos[2]) < actor.getClass().getSpeed(actor)*0.05
&& distance(dest, start) > 20;
float distToNextWaypoint = distance(start, dest);
bool isDestReached = (distToNextWaypoint <= destTolerance);
if (!isDestReached && mTimer > AI_REACTION_TIME)
{ {
if (doesPathNeedRecalc(dest, cell)) { //Only rebuild path if it's moved bool needPathRecalc = doesPathNeedRecalc(dest, cell);
mPathFinder.buildSyncedPath(start, dest, actor.getCell(), true); //Rebuild path, in case the target has moved
mPrevDest = dest; bool isWayClear = true;
if (!needPathRecalc) // TODO: add check if actor is actually shortcutting
{
isWayClear = checkWayIsClearForActor(start, dest, actor); // check if current shortcut is safe to follow
}
if (!isWayClear || needPathRecalc) // Only rebuild path if the target has moved or can't follow current shortcut
{
bool destInLOS = false;
if (isStuck || !isWayClear || !shortcutPath(start, dest, actor, &destInLOS))
{
mPathFinder.buildSyncedPath(start, dest, actor.getCell());
// give priority to go directly on target if there is minimal opportunity
if (destInLOS && mPathFinder.getPath().size() > 1)
{
// get point just before dest
std::list<ESM::Pathgrid::Point>::const_iterator pPointBeforeDest = mPathFinder.getPath().end();
--pPointBeforeDest;
--pPointBeforeDest;
// if start point is closer to the target then last point of path (excluding target itself) then go straight on the target
if (distance(start, dest) <= distance(dest, *pPointBeforeDest))
{
mPathFinder.clearPath();
mPathFinder.addPointToPath(dest);
}
}
}
} }
if(!mPathFinder.getPath().empty()) //Path has points in it if(!mPathFinder.getPath().empty()) //Path has points in it
@ -86,13 +126,22 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, ESM::Pathgrid::Po
//************************ //************************
/// Checks if you aren't moving; attempts to unstick you /// Checks if you aren't moving; attempts to unstick you
//************************ //************************
if(mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1])) //Path finished? if (isDestReached || mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1])) //Path finished?
{
actor.getClass().getMovementSettings(actor).mPosition[0] = 0; // stop moving
actor.getClass().getMovementSettings(actor).mPosition[1] = 0;
actor.getClass().getMovementSettings(actor).mPosition[2] = 0;
// turn to destination point
zTurn(actor, Ogre::Degree(getZAngleToPoint(start, dest)));
smoothTurn(actor, Ogre::Degree(getXAngleToPoint(start, dest)), 0);
return true; return true;
}
else if(mStuckTimer>0.5) //Every half second see if we need to take action to avoid something else if(mStuckTimer>0.5) //Every half second see if we need to take action to avoid something
{ {
/// TODO (tluppi#1#): Use ObstacleCheck here. Not working for some reason /// TODO (tluppi#1#): Use ObstacleCheck here. Not working for some reason
//if(mObstacleCheck.check(actor, duration)) { //if(mObstacleCheck.check(actor, duration)) {
if(distance(start, mStuckPos.pos[0], mStuckPos.pos[1], mStuckPos.pos[2]) < actor.getClass().getSpeed(actor)*0.05 && distance(dest, start) > 20) { //Actually stuck, and far enough away from destination to care if (isStuck) { //Actually stuck, and far enough away from destination to care
// first check if we're walking into a door // first check if we're walking into a door
MWWorld::Ptr door = getNearbyDoor(actor); MWWorld::Ptr door = getNearbyDoor(actor);
if(door != MWWorld::Ptr()) // NOTE: checks interior cells only if(door != MWWorld::Ptr()) // NOTE: checks interior cells only
@ -119,12 +168,94 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, ESM::Pathgrid::Po
actor.getClass().getMovementSettings(actor).mPosition[1] = 1; //Just run forward the rest of the time actor.getClass().getMovementSettings(actor).mPosition[1] = 1; //Just run forward the rest of the time
} }
// turn to next path point by X,Z axes
zTurn(actor, Ogre::Degree(mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]))); zTurn(actor, Ogre::Degree(mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1])));
smoothTurn(actor, Ogre::Degree(mPathFinder.getXAngleToNext(pos.pos[0], pos.pos[1], pos.pos[2])), 0);
return false; return false;
} }
bool MWMechanics::AiPackage::shortcutPath(const ESM::Pathgrid::Point& startPoint, const ESM::Pathgrid::Point& endPoint, const MWWorld::Ptr& actor, bool *destInLOS)
{
const MWWorld::Class& actorClass = actor.getClass();
MWBase::World* world = MWBase::Environment::get().getWorld();
// check if actor can move along z-axis
bool actorCanMoveByZ = (actorClass.canSwim(actor) && MWBase::Environment::get().getWorld()->isSwimming(actor))
|| world->isFlying(actor);
// don't use pathgrid when actor can move in 3 dimensions
bool isPathClear = actorCanMoveByZ;
if (!isPathClear
&& (!mShortcutProhibited || (PathFinder::MakeOgreVector3(mShortcutFailPos) - PathFinder::MakeOgreVector3(startPoint)).length() >= PATHFIND_SHORTCUT_RETRY_DIST))
{
// take the direct path only if there aren't any obstacles
isPathClear = !MWBase::Environment::get().getWorld()->castRay(
static_cast<float>(startPoint.mX), static_cast<float>(startPoint.mY), static_cast<float>(startPoint.mZ),
static_cast<float>(endPoint.mX), static_cast<float>(endPoint.mY), static_cast<float>(endPoint.mZ));
if (destInLOS != NULL) *destInLOS = isPathClear;
if (!isPathClear)
return false;
// check if an actor can move along the shortcut path
isPathClear = checkWayIsClearForActor(startPoint, endPoint, actor);
}
if (isPathClear) // can shortcut the path
{
mPathFinder.clearPath();
mPathFinder.addPointToPath(endPoint);
return true;
}
return false;
}
bool MWMechanics::AiPackage::checkWayIsClearForActor(const ESM::Pathgrid::Point& startPoint, const ESM::Pathgrid::Point& endPoint, const MWWorld::Ptr& actor)
{
bool actorCanMoveByZ = (actor.getClass().canSwim(actor) && MWBase::Environment::get().getWorld()->isSwimming(actor))
|| MWBase::Environment::get().getWorld()->isFlying(actor);
if (actorCanMoveByZ)
return true;
float actorSpeed = actor.getClass().getSpeed(actor);
float maxAvoidDist = AI_REACTION_TIME * actorSpeed + actorSpeed / MAX_VEL_ANGULAR.valueRadians() * 2; // *2 - for reliability
Ogre::Real distToTarget = Ogre::Vector3(static_cast<float>(endPoint.mX), static_cast<float>(endPoint.mY), 0).length();
float offsetXY = distToTarget > maxAvoidDist*1.5? maxAvoidDist : maxAvoidDist/2;
bool isClear = checkWayIsClear(PathFinder::MakeOgreVector3(startPoint), PathFinder::MakeOgreVector3(endPoint), offsetXY);
// update shortcut prohibit state
if (isClear)
{
if (mShortcutProhibited)
{
mShortcutProhibited = false;
mShortcutFailPos = ESM::Pathgrid::Point();
}
}
if (!isClear)
{
if (mShortcutFailPos.mX == 0 && mShortcutFailPos.mY == 0 && mShortcutFailPos.mZ == 0)
{
mShortcutProhibited = true;
mShortcutFailPos = startPoint;
}
}
return isClear;
}
bool MWMechanics::AiPackage::doesPathNeedRecalc(ESM::Pathgrid::Point dest, const ESM::Cell *cell) bool MWMechanics::AiPackage::doesPathNeedRecalc(ESM::Pathgrid::Point dest, const ESM::Cell *cell)
{ {
return distance(mPrevDest, dest) > 10; bool needRecalc = distance(mPrevDest, dest) > 10;
if (needRecalc)
mPrevDest = dest;
return needRecalc;
} }

View file

@ -24,6 +24,7 @@ namespace ESM
namespace MWMechanics namespace MWMechanics
{ {
const float AI_REACTION_TIME = 0.25f;
/// \brief Base class for AI packages /// \brief Base class for AI packages
class AiPackage class AiPackage
@ -70,7 +71,16 @@ namespace MWMechanics
protected: protected:
/// Causes the actor to attempt to walk to the specified location /// Causes the actor to attempt to walk to the specified location
/** \return If the actor has arrived at his destination **/ /** \return If the actor has arrived at his destination **/
bool pathTo(const MWWorld::Ptr& actor, ESM::Pathgrid::Point dest, float duration); bool pathTo(const MWWorld::Ptr& actor, const ESM::Pathgrid::Point& dest, float duration, float destTolerance = 0.0f);
/// Check if there aren't any obstacles along the path to make shortcut possible
/// If a shortcut is possible then path will be cleared and filled with the destination point.
/// \param destInLOS If not NULL function will return ray cast check result
/// \return If can shortcut the path
bool shortcutPath(const ESM::Pathgrid::Point& startPoint, const ESM::Pathgrid::Point& endPoint, const MWWorld::Ptr& actor, bool *destInLOS);
/// Check if the way to the destination is clear, taking into account actor speed
bool checkWayIsClearForActor(const ESM::Pathgrid::Point& startPoint, const ESM::Pathgrid::Point& endPoint, const MWWorld::Ptr& actor);
virtual bool doesPathNeedRecalc(ESM::Pathgrid::Point dest, const ESM::Cell *cell); virtual bool doesPathNeedRecalc(ESM::Pathgrid::Point dest, const ESM::Cell *cell);
@ -83,6 +93,9 @@ namespace MWMechanics
ESM::Position mStuckPos; ESM::Position mStuckPos;
ESM::Pathgrid::Point mPrevDest; ESM::Pathgrid::Point mPrevDest;
bool mShortcutProhibited; // shortcutting may be prohibited after unsuccessful attempt
ESM::Pathgrid::Point mShortcutFailPos; // position of last shortcut fail
}; };
} }

View file

@ -55,14 +55,10 @@ bool AiPursue::execute (const MWWorld::Ptr& actor, AiState& state, float duratio
//Set the target desition from the actor //Set the target desition from the actor
ESM::Pathgrid::Point dest = target.getRefData().getPosition().pos; ESM::Pathgrid::Point dest = target.getRefData().getPosition().pos;
if(distance(dest, pos.pos[0], pos.pos[1], pos.pos[2]) < 100) { //Stop when you get close if (pathTo(actor, dest, duration, 100)) {
actor.getClass().getMovementSettings(actor).mPosition[1] = 0; target.getClass().activate(target,actor).get()->execute(actor); //Arrest player when reached
target.getClass().activate(target,actor).get()->execute(actor); //Arrest player
return true; return true;
} }
else {
pathTo(actor, dest, duration); //Go to the destination
}
actor.getClass().getCreatureStats(actor).setMovementFlag(MWMechanics::CreatureStats::Flag_Run, true); //Make NPC run actor.getClass().getCreatureStats(actor).setMovementFlag(MWMechanics::CreatureStats::Flag_Run, true); //Make NPC run

View file

@ -413,7 +413,7 @@ namespace MWMechanics
ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(pos)); ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(pos));
// don't take shortcuts for wandering // don't take shortcuts for wandering
storage.mPathFinder.buildPath(start, dest, actor.getCell(), false); storage.mPathFinder.buildPath(start, dest, actor.getCell());
if(storage.mPathFinder.isPathConstructed()) if(storage.mPathFinder.isPathConstructed())
{ {
@ -520,7 +520,7 @@ namespace MWMechanics
ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(pos)); ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(pos));
// don't take shortcuts for wandering // don't take shortcuts for wandering
storage.mPathFinder.buildPath(start, dest, actor.getCell(), false); storage.mPathFinder.buildPath(start, dest, actor.getCell());
if(storage.mPathFinder.isPathConstructed()) if(storage.mPathFinder.isPathConstructed())
{ {

View file

@ -113,6 +113,49 @@ namespace MWMechanics
return sqrt(x * x + y * y + z * z); return sqrt(x * x + y * y + z * z);
} }
float getZAngleToDir(const Ogre::Vector3& dir)
{
return Ogre::Math::ATan2(dir.x,dir.y).valueDegrees();
}
float getXAngleToDir(const Ogre::Vector3& dir, float dirLen)
{
float len = (dirLen > 0.0f)? dirLen : dir.length();
return -Ogre::Math::ASin(dir.z / len).valueDegrees();
}
float getZAngleToPoint(const ESM::Pathgrid::Point &origin, const ESM::Pathgrid::Point &dest)
{
Ogre::Vector3 dir = PathFinder::MakeOgreVector3(dest) - PathFinder::MakeOgreVector3(origin);
return getZAngleToDir(dir);
}
float getXAngleToPoint(const ESM::Pathgrid::Point &origin, const ESM::Pathgrid::Point &dest)
{
Ogre::Vector3 dir = PathFinder::MakeOgreVector3(dest) - PathFinder::MakeOgreVector3(origin);
return getXAngleToDir(dir);
}
bool checkWayIsClear(const Ogre::Vector3& from, const Ogre::Vector3& to, float offsetXY)
{
if((to - from).length() >= PATHFIND_CAUTION_DIST || std::abs(from.z - to.z) <= PATHFIND_Z_REACH)
{
Ogre::Vector3 dir = to - from;
dir.z = 0;
dir.normalise();
float verticalOffset = 200; // instead of '200' here we want the height of the actor
Ogre::Vector3 _from = from + dir*offsetXY + Ogre::Vector3::UNIT_Z * verticalOffset;
// cast up-down ray and find height in world space of hit
float h = _from.z - MWBase::Environment::get().getWorld()->getDistToNearestRayHit(_from, -Ogre::Vector3::UNIT_Z, verticalOffset + PATHFIND_Z_REACH + 1);
if(std::abs(from.z - h) <= PATHFIND_Z_REACH)
return true;
}
return false;
}
PathFinder::PathFinder() PathFinder::PathFinder()
: mPathgrid(NULL), : mPathgrid(NULL),
mCell(NULL) mCell(NULL)
@ -165,23 +208,10 @@ namespace MWMechanics
*/ */
void PathFinder::buildPath(const ESM::Pathgrid::Point &startPoint, void PathFinder::buildPath(const ESM::Pathgrid::Point &startPoint,
const ESM::Pathgrid::Point &endPoint, const ESM::Pathgrid::Point &endPoint,
const MWWorld::CellStore* cell, const MWWorld::CellStore* cell)
bool allowShortcuts)
{ {
mPath.clear(); mPath.clear();
if(allowShortcuts)
{
// if there's a ray cast hit, can't take a direct path
if (!MWBase::Environment::get().getWorld()->castRay(
static_cast<float>(startPoint.mX), static_cast<float>(startPoint.mY), static_cast<float>(startPoint.mZ),
static_cast<float>(endPoint.mX), static_cast<float>(endPoint.mY), static_cast<float>(endPoint.mZ)))
{
mPath.push_back(endPoint);
return;
}
}
if(mCell != cell || !mPathgrid) if(mCell != cell || !mPathgrid)
{ {
mCell = cell; mCell = cell;
@ -270,6 +300,19 @@ namespace MWMechanics
return Ogre::Math::ATan2(directionX,directionY).valueDegrees(); return Ogre::Math::ATan2(directionX,directionY).valueDegrees();
} }
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.;
const ESM::Pathgrid::Point &nextPoint = *mPath.begin();
Ogre::Vector3 dir = MakeOgreVector3(nextPoint) - Ogre::Vector3(x,y,z);
return -Ogre::Math::ASin(dir.z / dir.length()).valueDegrees();
}
bool PathFinder::checkPathCompleted(float x, float y, float tolerance) bool PathFinder::checkPathCompleted(float x, float y, float tolerance)
{ {
if(mPath.empty()) if(mPath.empty())
@ -291,19 +334,18 @@ namespace MWMechanics
// see header for the rationale // see header for the rationale
void PathFinder::buildSyncedPath(const ESM::Pathgrid::Point &startPoint, void PathFinder::buildSyncedPath(const ESM::Pathgrid::Point &startPoint,
const ESM::Pathgrid::Point &endPoint, const ESM::Pathgrid::Point &endPoint,
const MWWorld::CellStore* cell, const MWWorld::CellStore* cell)
bool allowShortcuts)
{ {
if (mPath.size() < 2) if (mPath.size() < 2)
{ {
// if path has one point, then it's the destination. // if path has one point, then it's the destination.
// don't need to worry about bad path for this case // don't need to worry about bad path for this case
buildPath(startPoint, endPoint, cell, allowShortcuts); buildPath(startPoint, endPoint, cell);
} }
else else
{ {
const ESM::Pathgrid::Point oldStart(*getPath().begin()); const ESM::Pathgrid::Point oldStart(*getPath().begin());
buildPath(startPoint, endPoint, cell, allowShortcuts); buildPath(startPoint, endPoint, cell);
if (mPath.size() >= 2) if (mPath.size() >= 2)
{ {
// if 2nd waypoint of new path == 1st waypoint of old, // if 2nd waypoint of new path == 1st waypoint of old,

View file

@ -15,8 +15,24 @@ namespace MWWorld
namespace MWMechanics namespace MWMechanics
{ {
float distance(ESM::Pathgrid::Point point, float x, float y, float); float distance(ESM::Pathgrid::Point point, float x, float y, float z);
float distance(ESM::Pathgrid::Point a, ESM::Pathgrid::Point b); float distance(ESM::Pathgrid::Point a, ESM::Pathgrid::Point b);
float getZAngleToDir(const Ogre::Vector3& dir);
float getXAngleToDir(const Ogre::Vector3& dir, float dirLen = 0.0f);
float getZAngleToPoint(const ESM::Pathgrid::Point &origin, const ESM::Pathgrid::Point &dest);
float getXAngleToPoint(const ESM::Pathgrid::Point &origin, const ESM::Pathgrid::Point &dest);
const float PATHFIND_Z_REACH = 50.0f;
//static const float sMaxSlope = 49.0f; // duplicate as in physicssystem
// distance at which actor pays more attention to decide whether to shortcut or stick to pathgrid
const float PATHFIND_CAUTION_DIST = 500.0f;
// distance after which actor (failed previously to shortcut) will try again
const float PATHFIND_SHORTCUT_RETRY_DIST = 300.0f;
// cast up-down ray with some offset from actor position to check for pits/obstacles on the way to target;
// magnitude of pits/obstacles is defined by PATHFIND_Z_REACH
bool checkWayIsClear(const Ogre::Vector3& from, const Ogre::Vector3& to, float offsetXY);
class PathFinder class PathFinder
{ {
public: public:
@ -39,13 +55,15 @@ namespace MWMechanics
void clearPath(); void clearPath();
void buildPath(const ESM::Pathgrid::Point &startPoint, const ESM::Pathgrid::Point &endPoint, void buildPath(const ESM::Pathgrid::Point &startPoint, const ESM::Pathgrid::Point &endPoint,
const MWWorld::CellStore* cell, bool allowShortcuts = true); const MWWorld::CellStore* cell);
bool checkPathCompleted(float x, float y, float tolerance=32.f); bool checkPathCompleted(float x, float y, float tolerance=32.f);
///< \Returns true if we are within \a tolerance units of the last path point. ///< \Returns true if we are within \a tolerance units of the last path point.
float getZAngleToNext(float x, float y) const; float getZAngleToNext(float x, float y) const;
float getXAngleToNext(float x, float y, float z) const;
bool isPathConstructed() const bool isPathConstructed() const
{ {
return !mPath.empty(); return !mPath.empty();
@ -69,9 +87,9 @@ namespace MWMechanics
Which results in NPC "running in a circle" back to the just passed waypoint. Which results in NPC "running in a circle" back to the just passed waypoint.
*/ */
void buildSyncedPath(const ESM::Pathgrid::Point &startPoint, const ESM::Pathgrid::Point &endPoint, void buildSyncedPath(const ESM::Pathgrid::Point &startPoint, const ESM::Pathgrid::Point &endPoint,
const MWWorld::CellStore* cell, bool allowShortcuts = true); const MWWorld::CellStore* cell);
void addPointToPath(ESM::Pathgrid::Point &point) void addPointToPath(const ESM::Pathgrid::Point &point)
{ {
mPath.push_back(point); mPath.push_back(point);
} }