pathgrid shortcutting extended

actorid
mrcheko 11 years ago
parent 4e7ee97050
commit f811abb752

@ -407,6 +407,8 @@ namespace MWBase
virtual bool getLOS(const MWWorld::Ptr& npc,const MWWorld::Ptr& targetNpc) = 0;
///< get Line of Sight (morrowind stupid implementation)
virtual float getDistToNearestRayHit(const Ogre::Vector3& from, const Ogre::Vector3& dir, float maxDist) = 0;
virtual void enableActorCollision(const MWWorld::Ptr& actor, bool enable) = 0;
virtual int canRest() = 0;

@ -31,6 +31,40 @@ namespace
//chooses an attack depending on probability to avoid uniformity
void chooseBestAttack(const ESM::Weapon* weapon, MWMechanics::Movement &movement);
float getZAngleToDir(const Ogre::Vector3& dir, float dirLen = 0.0f)
{
float len = (dirLen >= 0.0f)? dirLen : dir.length();
return Ogre::Radian( Ogre::Math::ACos(dir.y / len) * sgn(Ogre::Math::ASin(dir.x / 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 offset)
{
if((to - from).length() >= PATHFIND_CAUTION_DIST || 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*offset + 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(abs(from.z - h) <= PATHFIND_Z_REACH)
return true;
}
return false;
}
}
namespace MWMechanics
@ -44,9 +78,10 @@ namespace MWMechanics
mReadyToAttack(false),
mStrike(false),
mCombatMove(false),
mRotate(false),
mMovement(),
mTargetAngle(0)
mTargetAngle(0),
mForceNoShortcut(false),
mShortcutFailPos()
{
}
@ -71,13 +106,12 @@ namespace MWMechanics
mCombatMove = false;
}
}
actor.getClass().getMovementSettings(actor) = mMovement;
if (mRotate)
if(actor.getRefData().getPosition().rot[2] != mTargetAngle)
{
if (zTurn(actor, Ogre::Degree(mTargetAngle)))
mRotate = false;
zTurn(actor, Ogre::Degree(mTargetAngle));
}
@ -92,7 +126,7 @@ namespace MWMechanics
}
//Update with period = tReaction
mTimerReact = 0;
//actual attacking logic
@ -136,6 +170,7 @@ namespace MWMechanics
actor.getClass().getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, true);
// Get weapon characteristics
if (actor.getClass().hasInventoryStore(actor))
{
MWMechanics::DrawState_ state = actor.getClass().getCreatureStats(actor).getDrawState();
@ -166,15 +201,13 @@ namespace MWMechanics
weapRange = 150; //TODO: use true attack range (the same problem in Creature::hit)
}
ESM::Position pos = actor.getRefData().getPosition();
float rangeMelee;
float rangeCloseUp;
bool distantCombat = false;
if (weaptype==WeapType_BowAndArrow || weaptype==WeapType_Crossbow || weaptype==WeapType_Thrown)
{
rangeMelee = 1000; // TODO: should depend on archer skill
rangeCloseUp = 0; //doesn't needed when attacking from distance
rangeCloseUp = 0; // not needed in ranged combat
distantCombat = true;
}
else
@ -182,23 +215,27 @@ namespace MWMechanics
rangeMelee = weapRange;
rangeCloseUp = 300;
}
ESM::Position pos = actor.getRefData().getPosition();
Ogre::Vector3 vActorPos(pos.pos);
Ogre::Vector3 vTargetPos(mTarget.getRefData().getPosition().pos);
Ogre::Vector3 vDirToTarget = vTargetPos - vActorPos;
float distToTarget = vDirToTarget.length();
Ogre::Vector3 vStart(pos.pos[0], pos.pos[1], pos.pos[2]);
ESM::Position targetPos = mTarget.getRefData().getPosition();
Ogre::Vector3 vDest(targetPos.pos[0], targetPos.pos[1], targetPos.pos[2]);
Ogre::Vector3 vDir = vDest - vStart;
float distBetween = vDir.length();
bool isStuck = false;
float speed = 0.0f;
if(mMovement.mPosition[1] && (Ogre::Vector3(mLastPos.pos) - vActorPos).length() < (speed = cls.getSpeed(actor)) / 10.0f)
isStuck = true;
if(distBetween < rangeMelee || (distBetween <= rangeCloseUp && mFollowTarget) )
mLastPos = pos;
if(distToTarget < rangeMelee || (distToTarget <= rangeCloseUp && mFollowTarget && !isStuck) )
{
//Melee and Close-up combat
vDir.z = 0;
float dirLen = vDir.length();
mTargetAngle = Ogre::Radian( Ogre::Math::ACos(vDir.y / dirLen) * sgn(Ogre::Math::ASin(vDir.x / dirLen)) ).valueDegrees();
mRotate = true;
vDirToTarget.z = 0;
mTargetAngle = getZAngleToDir(vDirToTarget, distToTarget);
//bool LOS = MWBase::Environment::get().getWorld()->getLOS(actor, mTarget);
if (mFollowTarget && distBetween > rangeMelee)
if (mFollowTarget && distToTarget > rangeMelee)
{
//Close-up combat: just run up on target
mMovement.mPosition[1] = 1;
@ -228,7 +265,7 @@ namespace MWMechanics
}
}
if(distantCombat && distBetween < rangeMelee/4)
if(distantCombat && distToTarget < rangeMelee/4)
{
mMovement.mPosition[1] = -1;
}
@ -238,32 +275,80 @@ namespace MWMechanics
mFollowTarget = true;
}
}
else
else // remote pathfinding
{
//target is at far distance: build path to target OR follow target (if previously actor had reached it once)
mFollowTarget = false;
bool preferShortcut = false;
bool inLOS;
if(mReadyToAttack) isStuck = false;
// check if shortcut is available
if(!isStuck
&& (!mForceNoShortcut
|| (Ogre::Vector3(mShortcutFailPos.pos) - vActorPos).length() >= PATHFIND_SHORTCUT_RETRY_DIST)
&& (inLOS = MWBase::Environment::get().getWorld()->getLOS(actor, mTarget)))
{
if(speed == 0.0f) speed = cls.getSpeed(actor);
// maximum dist before pit/obstacle for actor to avoid them depending on his speed
float maxAvoidDist = tReaction * speed + speed / MAX_VEL_ANGULAR.valueRadians() * 2;
//if(actor.getRefData().getPosition().rot[2] != mTargetAngle)
preferShortcut = checkWayIsClear(vActorPos, vTargetPos, distToTarget > maxAvoidDist*1.5? maxAvoidDist : maxAvoidDist/2);
}
if(preferShortcut)
{
mTargetAngle = getZAngleToDir(vDirToTarget, distToTarget);
mForceNoShortcut = false;
mShortcutFailPos.pos[0] = mShortcutFailPos.pos[1] = mShortcutFailPos.pos[2] = 0;
if(mPathFinder.isPathConstructed())
mPathFinder.clearPath();
}
else // if shortcut failed stick to path grid
{
if(!isStuck && mShortcutFailPos.pos[0] == 0.0f && mShortcutFailPos.pos[1] == 0.0f && mShortcutFailPos.pos[2] == 0.0f)
{
mForceNoShortcut = true;
mShortcutFailPos = pos;
}
buildNewPath(actor); //may fail to build a path, check before use
mFollowTarget = false;
//delete visited path node
mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1],pos.pos[2]);
buildNewPath(actor); //may fail to build a path, check before use
//if no new path leave mTargetAngle unchanged
if(!mPathFinder.getPath().empty())
{
//try shortcut
if(vDir.length() < mPathFinder.getDistToNext(pos.pos[0],pos.pos[1],pos.pos[2]) && MWBase::Environment::get().getWorld()->getLOS(actor, mTarget))
mTargetAngle = Ogre::Radian( Ogre::Math::ACos(vDir.y / vDir.length()) * sgn(Ogre::Math::ASin(vDir.x / vDir.length())) ).valueDegrees();
else
mTargetAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]);
mRotate = true;
//delete visited path node
mPathFinder.checkWaypoint(pos.pos[0],pos.pos[1],pos.pos[2]);
// This works on the borders between the path grid and areas with no waypoints.
if(inLOS && mPathFinder.getPath().size() > 1)
{
// get point just before target
std::list<ESM::Pathgrid::Point>::iterator pntIter = --mPathFinder.getPath().end();
--pntIter;
Ogre::Vector3 vBeforeTarget = Ogre::Vector3(pntIter->mX, pntIter->mY, pntIter->mZ);
// if current actor pos is closer to target then last point of path (excluding target itself) then go straight on target
if(distToTarget <= (vTargetPos - vBeforeTarget).length())
{
mTargetAngle = getZAngleToDir(vDirToTarget, distToTarget);
preferShortcut = true;
}
}
// if there is no new path, then go straight on target
if(!preferShortcut)
{
if(!mPathFinder.getPath().empty())
mTargetAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]);
else
mTargetAngle = getZAngleToDir(vDirToTarget, distToTarget);
}
}
mMovement.mPosition[1] = 1;
mReadyToAttack = false;
}
if(distBetween > rangeMelee)
if(distToTarget > rangeMelee)
{
//special run attack; it shouldn't affect melee combat tactics
if(actor.getClass().getMovementSettings(actor).mPosition[1] == 1)
@ -277,7 +362,7 @@ namespace MWMechanics
&& mTarget.getClass().getMovementSettings(mTarget).mPosition[1] == 0)
speed2 = 0;
float s1 = distBetween - weapRange;
float s1 = distToTarget - weapRange;
float t = s1/speed1;
float s2 = speed2 * t;
float t_swing = 0.17f/weapSpeed;//instead of 0.17 should be the time of playing weapon anim from 'start' to 'hit' tags
@ -300,31 +385,23 @@ namespace MWMechanics
void AiCombat::buildNewPath(const MWWorld::Ptr& actor)
{
//Construct path to target
ESM::Pathgrid::Point dest;
dest.mX = mTarget.getRefData().getPosition().pos[0];
dest.mY = mTarget.getRefData().getPosition().pos[1];
dest.mZ = mTarget.getRefData().getPosition().pos[2];
Ogre::Vector3 newPathTarget = Ogre::Vector3(dest.mX, dest.mY, dest.mZ);
float dist = -1; //hack to indicate first time, to construct a new path
Ogre::Vector3 newPathTarget = Ogre::Vector3(mTarget.getRefData().getPosition().pos);
float dist;
if(!mPathFinder.getPath().empty())
{
ESM::Pathgrid::Point lastPt = mPathFinder.getPath().back();
Ogre::Vector3 currPathTarget(lastPt.mX, lastPt.mY, lastPt.mZ);
dist = Ogre::Math::Abs((newPathTarget - currPathTarget).length());
dist = (newPathTarget - currPathTarget).length();
}
else dist = 1e+38F; // necessarily construct a new path
float targetPosThreshold;
bool isOutside = actor.getCell()->getCell()->isExterior();
if (isOutside)
targetPosThreshold = 300;
else
targetPosThreshold = 100;
float targetPosThreshold = (actor.getCell()->getCell()->isExterior())? 300 : 100;
if((dist < 0) || (dist > targetPosThreshold))
//construct new path only if target has moved away more than on [targetPosThreshold]
if(dist > targetPosThreshold)
{
//construct new path only if target has moved away more than on <targetPosThreshold>
ESM::Position pos = actor.getRefData().getPosition();
ESM::Pathgrid::Point start;
@ -332,17 +409,18 @@ namespace MWMechanics
start.mY = pos.pos[1];
start.mZ = pos.pos[2];
ESM::Pathgrid::Point dest;
dest.mX = newPathTarget.x;
dest.mY = newPathTarget.y;
dest.mZ = newPathTarget.z;
if(!mPathFinder.isPathConstructed())
mPathFinder.buildPath(start, dest, actor.getCell(), isOutside);
mPathFinder.buildPath(start, dest, actor.getCell(), false);
else
{
PathFinder newPathFinder;
newPathFinder.buildPath(start, dest, actor.getCell(), isOutside);
newPathFinder.buildPath(start, dest, actor.getCell(), false);
//TO EXPLORE:
//maybe here is a mistake (?): PathFinder::getPathSize() returns number of grid points in the path,
//not the actual path length. Here we should know if the new path is actually more effective.
//if(pathFinder2.getPathSize() < mPathFinder.getPathSize())
if(!mPathFinder.getPath().empty())
{
newPathFinder.syncStart(mPathFinder.getPath());

@ -43,8 +43,11 @@ namespace MWMechanics
bool mReadyToAttack, mStrike;
bool mFollowTarget;
bool mCombatMove;
bool mRotate;
bool mForceNoShortcut;
ESM::Position mShortcutFailPos;
ESM::Position mLastPos;
MWMechanics::Movement mMovement;
MWWorld::Ptr mTarget;

@ -384,22 +384,21 @@ namespace MWMechanics
return false;
}
void PathFinder::syncStart(const std::list<ESM::Pathgrid::Point> &path)
bool PathFinder::syncStart(const std::list<ESM::Pathgrid::Point> &path)
{
if (mPath.size() < 2)
return; //nothing to pop
return false; //nothing to pop
std::list<ESM::Pathgrid::Point>::const_iterator oldStart = path.begin();
std::list<ESM::Pathgrid::Point>::iterator iter = ++mPath.begin();
if( (*iter).mX == oldStart->mX
&& (*iter).mY == oldStart->mY
&& (*iter).mZ == oldStart->mZ
&& (*iter).mAutogenerated == oldStart->mAutogenerated
&& (*iter).mConnectionNum == oldStart->mConnectionNum )
&& (*iter).mZ == oldStart->mZ)
{
mPath.pop_front();
return true;
}
return false;
}
}

@ -64,10 +64,15 @@ namespace MWMechanics
return mPath;
}
//When first point of newly created path is the nearest to actor point, then
//the cituation can occure when this point is undesirable (if the 2nd point of new path == the 1st point of old path)
//This functions deletes that point.
void syncStart(const std::list<ESM::Pathgrid::Point> &path);
/** Synchronize new path with old one to avoid visiting 1 waypoint 2 times
@note
If the first point is chosen as the nearest one
the cituation can occure when the 1st point of the new path is undesirable
(i.e. the 2nd point of new path == the 1st point of old path).
@param path - old path
@return true if such point was found and deleted
*/
bool syncStart(const std::list<ESM::Pathgrid::Point> &path);
void addPointToPath(ESM::Pathgrid::Point &point)
{

@ -31,8 +31,7 @@ bool zTurn(const MWWorld::Ptr& actor, Ogre::Radian targetAngle)
if (absDiff < epsilon)
return true;
// Max. speed of 10 radian per sec
Ogre::Radian limit = Ogre::Radian(10) * MWBase::Environment::get().getFrameDuration();
Ogre::Radian limit = MAX_VEL_ANGULAR * MWBase::Environment::get().getFrameDuration();
if (absDiff > limit)
diff = Ogre::Math::Sign(diff) * limit;

@ -10,6 +10,9 @@ class Ptr;
namespace MWMechanics
{
// Max rotating speed, radian/sec
const Ogre::Radian MAX_VEL_ANGULAR(10);
/// configure rotation settings for an actor to reach this target angle (eventually)
/// @return have we reached the target angle?
bool zTurn(const MWWorld::Ptr& actor, Ogre::Radian targetAngle);

@ -1875,7 +1875,7 @@ namespace MWWorld
out.push_back(searchPtrViaHandle(*it));
}
}
bool World::getLOS(const MWWorld::Ptr& npc,const MWWorld::Ptr& targetNpc)
{
if (!targetNpc.getRefData().isEnabled() || !npc.getRefData().isEnabled())
@ -1893,6 +1893,19 @@ namespace MWWorld
return false;
}
float World::getDistToNearestRayHit(const Ogre::Vector3& from, const Ogre::Vector3& dir, float maxDist)
{
btVector3 btFrom(from.x, from.y, from.z);
btVector3 btTo = btVector3(dir.x, dir.y, dir.z);
btTo.normalize();
btTo = btFrom + btTo * maxDist;
std::pair<std::string, float> result = mPhysEngine->rayTest(btFrom, btTo, false);
if(result.second == -1) return maxDist;
else return result.second*(btTo-btFrom).length();
}
void World::enableActorCollision(const MWWorld::Ptr& actor, bool enable)
{
OEngine::Physic::PhysicActor *physicActor = mPhysEngine->getCharacter(actor.getRefData().getHandle());

@ -510,6 +510,8 @@ namespace MWWorld
virtual bool getLOS(const MWWorld::Ptr& npc,const MWWorld::Ptr& targetNpc);
///< get Line of Sight (morrowind stupid implementation)
virtual float getDistToNearestRayHit(const Ogre::Vector3& from, const Ogre::Vector3& dir, float maxDist);
virtual void enableActorCollision(const MWWorld::Ptr& actor, bool enable);
virtual int canRest();

Loading…
Cancel
Save