mirror of
https://github.com/tribes2/engine.git
synced 2026-03-02 20:10:25 +00:00
t2 engine svn checkout
This commit is contained in:
commit
ff569bd2ae
988 changed files with 394180 additions and 0 deletions
831
ai/graphPath.cc
Normal file
831
ai/graphPath.cc
Normal file
|
|
@ -0,0 +1,831 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// V12 Engine
|
||||
//
|
||||
// Copyright (c) 2001 GarageGames.Com
|
||||
// Portions Copyright (c) 2001 by Sierra Online, Inc.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#include "ai/graph.h"
|
||||
#include "platform/profiler.h"
|
||||
|
||||
//-------------------------------------------------------------------------------------
|
||||
|
||||
// Go through the construct function since we want to reconstruct on mission cycle.
|
||||
NavigationPath::NavigationPath()
|
||||
{
|
||||
constructThis();
|
||||
}
|
||||
|
||||
void NavigationPath::constructThis()
|
||||
{
|
||||
mState.constructThis();
|
||||
mCurEdge = NULL;
|
||||
mTimeSlice = 0;
|
||||
mSaveSeekNode = -1;
|
||||
mSearchDist = 0.0f;
|
||||
mUserStuck = true;
|
||||
mForceSearch = true;
|
||||
mRepathCounter = 10000;
|
||||
mSaveDest.set(1e7, 1e7, 1e7);
|
||||
setRedoDist();
|
||||
mPctThreshSqrd = 1.0;
|
||||
mPathIndoors = mAreIndoors = false;
|
||||
mAwaitingSearch = false;
|
||||
mTeam = 0;
|
||||
mBusyJetting = false;
|
||||
mSearchWasValid = true;
|
||||
mAdjustSeek.set(0,0,0);
|
||||
dMemset(mSavedEndpoints, 0, sizeof(mSavedEndpoints));
|
||||
GraphEdge newEdge;
|
||||
mSaveLastEdge = newEdge;
|
||||
mStopCounter = 0;
|
||||
mCastZ = mCastAng = 0.0;
|
||||
mCastAverage.set(0,0,0);
|
||||
mEstimatedEdge = NULL;
|
||||
mEstimatedEnergy = 0.0;
|
||||
mFindHere.init();
|
||||
}
|
||||
|
||||
// Since some of the code is in elsewhere (graphSmooth), we should package up the
|
||||
// relevant state for it... Doesn't seem like the best organization, but the idea is
|
||||
// that the volume code "knows" more about smoothing paths indoor than we do here,
|
||||
// and so that code should be separated...
|
||||
void NavigationPath::State::constructThis()
|
||||
{
|
||||
thisEdgeJetting = false;
|
||||
nextEdgeJetting = false;
|
||||
nextEdgePrecise = false;
|
||||
curSeekNode = 0;
|
||||
seekLoc.set(0,0,0);
|
||||
path.clear();
|
||||
visit.clear();
|
||||
edges.clear();
|
||||
hereLoc.set(0,0,0);
|
||||
destLoc.set(0,0,0);
|
||||
}
|
||||
|
||||
// Fetch the node along the path, undoing any bit-flipped Transient indices.
|
||||
GraphNode * NavigationPath::getNode(S32 idx)
|
||||
{
|
||||
S32 node = mState.path[idx];
|
||||
S32 toggle = -S32(node < 0);
|
||||
return gNavGraph->lookupNode(node ^ toggle);
|
||||
}
|
||||
|
||||
// Get location in path, handling case where endpoints were transients.
|
||||
Point3F NavigationPath::getLoc(S32 idx)
|
||||
{
|
||||
S32 node = mState.path[idx];
|
||||
if(node >= 0)
|
||||
return gNavGraph->lookupNode(node)->location();
|
||||
else
|
||||
return mSavedEndpoints[idx > 0];
|
||||
}
|
||||
|
||||
// Save endpoints of search (flagged with negative indices). A NULL dstNode
|
||||
// is passed for searches where destination is a node in graph.
|
||||
void NavigationPath::saveEndpoints(TransientNode* srcNode, TransientNode* dstNode)
|
||||
{
|
||||
mSavedEndpoints[0] = srcNode->location();
|
||||
mState.path.first() ^= S32(-1);
|
||||
if (dstNode)
|
||||
{
|
||||
mSavedEndpoints[1] = dstNode->location();
|
||||
mState.path.last() ^= S32(-1);
|
||||
}
|
||||
|
||||
// These edges are used for the path advancing. Must take care with edges
|
||||
// coming off of the transients - the last one must be saved since it is popped
|
||||
// after the search (source connection INTO graph remains though)
|
||||
setSizeAndClear(mState.edges, getMax(mState.path.size()-1, 0));
|
||||
for (S32 i = mState.edges.size(); i > 0; i--)
|
||||
{
|
||||
GraphEdge * edgePtr = getNode(i-1)->getEdgeTo(getNode(i));
|
||||
AssertFatal(edgePtr, "All edges should exist in path");
|
||||
mState.edges[i - 1] = edgePtr;
|
||||
}
|
||||
if (mState.edges.size())
|
||||
{
|
||||
mSaveLastEdge = * mState.edges.last();
|
||||
mState.edges.last() = & mSaveLastEdge;
|
||||
}
|
||||
}
|
||||
|
||||
// This is for renderer - though we'll use a similar node marking scheme for
|
||||
// implementing avoidance / randomization of paths.
|
||||
void NavigationPath::markRenderPath()
|
||||
{
|
||||
for(S32 i = getMax(mState.curSeekNode - 1, 0); i < mState.path.size(); i++)
|
||||
getNode(i)->setOnPath();
|
||||
}
|
||||
|
||||
// Revised path search to go off of the transient nodes.
|
||||
void NavigationPath::computePath(TransientNode& srcNode, TransientNode& dstNode)
|
||||
{
|
||||
mSearchDist = 0.0f;
|
||||
mSearchWasValid = false;
|
||||
|
||||
// Perform the search if the push indicates these two can (probably) reach.
|
||||
if (gNavGraph->pushTransientPair(srcNode, dstNode, mTeam, mJetCaps))
|
||||
{
|
||||
PROFILE_START(PathComputation);
|
||||
|
||||
mState.path.clear();
|
||||
mState.curSeekNode = 0;
|
||||
GraphSearch * searcher = gNavGraph->getMainSearcher();
|
||||
searcher->setAStar(true);
|
||||
searcher->setTeam(mTeam);
|
||||
searcher->setThreats(gNavGraph->getThreatSet(mTeam));
|
||||
searcher->setRandomize(true);
|
||||
#if _GRAPH_PART_
|
||||
searcher->setRatings(gNavGraph->jetManager().getRatings(mJetCaps));
|
||||
#endif
|
||||
|
||||
searcher->performSearch(&srcNode, &dstNode);
|
||||
|
||||
// Path fetcher tells us if search failed.
|
||||
if (searcher->getPathIndices(mState.path))
|
||||
{
|
||||
|
||||
|
||||
|
||||
mSearchDist = searcher->searchDist();
|
||||
setSizeAndClear(mState.visit, mState.path.size());
|
||||
saveEndpoints(&srcNode, &dstNode);
|
||||
mState.curSeekNode = 1;
|
||||
setEdgeConstraints();
|
||||
mAwaitingSearch = false;
|
||||
mSearchWasValid = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Note - we need to know if the transient connections into the graph were
|
||||
// all make-able by the bot (they should all go through the same evaluation
|
||||
// to check the connections). I think this assert arose because a bot had
|
||||
// transient connection that couldn't be made.
|
||||
// // A search should only fail due to force fields. Armor capabilities should be
|
||||
// // adequately predicted in advance.
|
||||
// AssertFatal(gNavGraph->haveForceFields(), "Failed to predict failure");
|
||||
//
|
||||
// // Force field management will update this team's partition accordingly.
|
||||
// gNavGraph->newPartition(searcher, mTeam);
|
||||
|
||||
if (gNavGraph->haveForceFields())
|
||||
{
|
||||
// Force field management will update this team's partition accordingly.
|
||||
gNavGraph->newPartition(searcher, mTeam);
|
||||
}
|
||||
}
|
||||
PROFILE_END(); // PathComputation
|
||||
}
|
||||
else {
|
||||
#if _GRAPH_WARNINGS_
|
||||
NavigationGraph::warning("Search tried across islands or partitions");
|
||||
#endif
|
||||
}
|
||||
|
||||
// Unconnects from graph.
|
||||
gNavGraph->popTransientPair(srcNode, dstNode);
|
||||
}
|
||||
|
||||
// Just want all the post-pathCompute stuff separated out:
|
||||
void NavigationPath::afterCompute()
|
||||
{
|
||||
mSaveDest = mState.destLoc;
|
||||
mForceSearch = false;
|
||||
mRepathCounter = 0;
|
||||
if ( mRedoMode == OnPercent ) {
|
||||
F32 pctThresh = (mRedoPercent * mSearchDist);
|
||||
mPctThreshSqrd = (pctThresh * pctThresh);
|
||||
}
|
||||
mUserStuck = false;
|
||||
}
|
||||
|
||||
bool NavigationPath::updateTransients(TransientNode& hereNode,
|
||||
TransientNode& destNode, bool forceRedo)
|
||||
{
|
||||
// Nodes could be invalid-
|
||||
if (mHook.iGrowOld())
|
||||
mLocateHere.reset(), mLocateDest.reset();
|
||||
|
||||
if (forceRedo)
|
||||
mLocateHere.forceCheck(), mLocateDest.forceCheck();
|
||||
|
||||
// This does all the work of figuring out how to connect-
|
||||
mLocateHere.update(mState.hereLoc);
|
||||
mLocateDest.update(mState.destLoc);
|
||||
|
||||
// Install connections found, and update locs-
|
||||
hereNode.setEdges(mLocateHere.getEdges());
|
||||
destNode.setEdges(mLocateDest.getEdges());
|
||||
hereNode.setClosest(mLocateHere.bestMatch());
|
||||
destNode.setClosest(mLocateDest.bestMatch());
|
||||
hereNode.setLoc(mState.hereLoc);
|
||||
destNode.setLoc(mState.destLoc);
|
||||
|
||||
// NOTE! Only hook one way due to how Push-Search-Pop works (computePath() above).
|
||||
bool needToJet;
|
||||
if (mLocateHere.canHookTo(mLocateDest, needToJet)) {
|
||||
GraphEdge& edge = hereNode.pushEdge(&destNode);
|
||||
if (needToJet)
|
||||
edge.setJetting();
|
||||
gNavGraph->jetManager().initEdge(edge, &hereNode, &destNode);
|
||||
}
|
||||
|
||||
return forceRedo; // not used
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------------------
|
||||
// Periodic path updates. Return true if there are nodes to traverse.
|
||||
|
||||
#define SearchWaitTicks 20
|
||||
#define AllowVisitTicks 21
|
||||
|
||||
bool NavigationPath::checkPathUpdate(TransientNode& hereNode, TransientNode& destNode)
|
||||
{
|
||||
bool recompute = mForceSearch;
|
||||
// bool recompute = false;
|
||||
|
||||
bool changedSeekNode = (mSaveSeekNode != mState.curSeekNode);
|
||||
mSaveSeekNode = mState.curSeekNode;
|
||||
|
||||
if (!recompute) {
|
||||
F32 threshold = (mRedoMode == OnDist ? mRedoDistSqrd : mPctThreshSqrd);
|
||||
bool canSearch = (++mRepathCounter > SearchWaitTicks) && !userMustJet();
|
||||
bool needSearch = ((mState.destLoc - mSaveDest).lenSquared() > threshold);
|
||||
|
||||
if (mUserStuck)
|
||||
needSearch = true;
|
||||
|
||||
if (needSearch) {
|
||||
// We need to make the canSearch check a little more strict and wait a little
|
||||
// extra time, or search when the node has been updated.
|
||||
if (canSearch && (changedSeekNode || mRepathCounter > AllowVisitTicks))
|
||||
recompute = true;
|
||||
else
|
||||
mAwaitingSearch = true;
|
||||
}
|
||||
}
|
||||
if (recompute)
|
||||
mAwaitingSearch = true;
|
||||
|
||||
updateTransients(hereNode, destNode, recompute);
|
||||
|
||||
if (recompute) {
|
||||
computePath(hereNode, destNode);
|
||||
afterCompute();
|
||||
}
|
||||
|
||||
if (weAreIndoors())
|
||||
mAreIndoors = true;
|
||||
else {
|
||||
if(checkOutsideAdvance())
|
||||
advanceByOne();
|
||||
}
|
||||
|
||||
// Update path
|
||||
if (mState.curSeekNode < mState.path.size())
|
||||
{
|
||||
GraphNode * fromNode = (mState.curSeekNode > 0 ? getNode(mState.curSeekNode-1) : NULL);
|
||||
|
||||
if (fromNode && gNavGraph->useVolumeTraverse(fromNode, mCurEdge))
|
||||
{
|
||||
S32 adv = gNavGraph->checkIndoorSkip(mState);
|
||||
|
||||
if (adv)
|
||||
{
|
||||
// Con::printf("Advanced through %d indoor nodes", adv);
|
||||
while (adv--)
|
||||
{
|
||||
AssertFatal(canAdvance(), "Nav path tried illegal advance");
|
||||
advanceByOne();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (gNavGraph->volumeTraverse(fromNode, mCurEdge, mState))
|
||||
{
|
||||
if (canAdvance()) //==> Shouldn't it always be possible to advance?
|
||||
advanceByOne();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mState.seekLoc = getLoc(mState.curSeekNode);
|
||||
F32 threshold = visitRadius() + 0.22;
|
||||
if(within_2D(mState.hereLoc, mState.seekLoc, threshold) && canAdvance()) {
|
||||
advanceByOne();
|
||||
if (mState.curSeekNode < mState.path.size())
|
||||
mState.seekLoc = getLoc(mState.curSeekNode); // re-fetch!
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (mState.curSeekNode < mState.path.size());
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------------------
|
||||
|
||||
// The consumer of the NavigationPath must call this every frame.
|
||||
bool NavigationPath::updateLocations(const Point3F& here, const Point3F& there)
|
||||
{
|
||||
// This addition to the Z is important because we don't match to indoor nodes that
|
||||
// we are below. Not pretty, but the alternative is most extra LOS calls, or other
|
||||
// potential ambiguities in the node matching process. Also see the wall scanning
|
||||
// code below, which is 'cognizant' of these numbers.
|
||||
(mState.hereLoc = here).z += 0.4;
|
||||
(mState.destLoc = there).z += 0.4;
|
||||
|
||||
mAreIndoors = false; // (default - get's changed if indoors)
|
||||
|
||||
if( !NavigationGraph::gotOneWeCanUse()) {
|
||||
mState.seekLoc = mState.destLoc;
|
||||
mSearchWasValid = false;
|
||||
}
|
||||
else {
|
||||
// Threat manager needs to be called frequently-
|
||||
gNavGraph->threats()->monitorThreats();
|
||||
gNavGraph->monitorForceFields();
|
||||
|
||||
// Note hook nodes must be fetched every frame (in case of graph revisions).
|
||||
if (!checkPathUpdate(mHook.getHook1(), mHook.getHook2()))
|
||||
mState.seekLoc = mState.destLoc;
|
||||
|
||||
// Debug info-
|
||||
markRenderPath();
|
||||
|
||||
// This just walks the graph looking for malfeasance
|
||||
// gNavGraph->patrolForProblems();
|
||||
}
|
||||
|
||||
// Movement code sets while jetting, each set lasts for one frame
|
||||
mBusyJetting = false;
|
||||
|
||||
// Busy-dec the stop counter, and check wall avoidance when non-zero.
|
||||
if (--mStopCounter <= 0)
|
||||
mStopCounter = 0;
|
||||
else
|
||||
checkWallAvoid();
|
||||
|
||||
// Return status of the last search performed-
|
||||
return mSearchWasValid;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------------------
|
||||
// Management of path advancing.
|
||||
|
||||
|
||||
// This assumes we have a graph and are seeking a node within the list.
|
||||
S32 NavigationPath::checkOutsideAdvance()
|
||||
{
|
||||
if(canAdvance())
|
||||
{
|
||||
S32 seekNode = mState.curSeekNode + 1;
|
||||
if (seekNode < mState.path.size() && !mState.nextEdgeJetting)
|
||||
{ // See if we can get to the next one. We're doing this in 2D, which may be Ok
|
||||
// in the long run too, though we might track maximum heights along the way.
|
||||
// First we check for registered threats along this segment.
|
||||
Point3F srcLoc(mState.hereLoc);
|
||||
Point3F dstLoc(getLoc(seekNode));
|
||||
if (gNavGraph->threats()->sanction(srcLoc, dstLoc, mTeam))
|
||||
{
|
||||
srcLoc.z = dstLoc.z = 0.0f;
|
||||
F32 pct = gNavGraph->mTerrainInfo.checkOpenTerrain(srcLoc, dstLoc);
|
||||
if (pct == 1.0)
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool NavigationPath::canAdvance()
|
||||
{
|
||||
if (mState.curSeekNode < mState.path.size())
|
||||
if (!mState.thisEdgeJetting)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
F32 NavigationPath::visitRadius()
|
||||
{
|
||||
// if (mState.nextEdgeJetting || mAreIndoors || mPathIndoors)
|
||||
if (mState.nextEdgePrecise || mAreIndoors || mPathIndoors)
|
||||
return 0.4;
|
||||
else
|
||||
return 2.6;
|
||||
}
|
||||
|
||||
// See if the next destination is a Jetting edge. An assumption of this routine
|
||||
// is that it is only called when a new path is computed, or when the path is
|
||||
// advanced. Else problems - such as transient source having new connections
|
||||
// which don't relate to the current path, etc.
|
||||
void NavigationPath::setEdgeConstraints()
|
||||
{
|
||||
mState.thisEdgeJetting = false;
|
||||
mState.nextEdgeJetting = false;
|
||||
mState.nextEdgePrecise = false;
|
||||
mPathIndoors = false;
|
||||
mCurEdge = NULL;
|
||||
|
||||
if (mState.curSeekNode > 0 && mState.curSeekNode < mState.path.size())
|
||||
{
|
||||
GraphNode * curSeekNode = getNode(mState.curSeekNode);
|
||||
GraphNode * prevSeekNode = getNode(mState.curSeekNode-1);
|
||||
|
||||
mCurEdge = mState.edges[mState.curSeekNode - 1];
|
||||
|
||||
if (curSeekNode->indoor() || prevSeekNode->indoor())
|
||||
mPathIndoors = true;
|
||||
|
||||
mState.thisEdgeJetting = mCurEdge->isJetting();
|
||||
|
||||
// Configure the edge. Hop over is a U8 with 3 bits of precision.
|
||||
if (mState.thisEdgeJetting)
|
||||
{
|
||||
mNavJetting.init();
|
||||
if (mCurEdge->hasHop())
|
||||
mNavJetting.mHopOver = mCurEdge->getHop();
|
||||
}
|
||||
|
||||
if (mState.thisEdgeJetting)
|
||||
mState.seekLoc = getLoc(mState.curSeekNode);
|
||||
|
||||
if (mState.curSeekNode+1 < mState.path.size())
|
||||
{
|
||||
GraphEdge * nextEdge = mState.edges[mState.curSeekNode];
|
||||
mState.nextEdgeJetting = nextEdge->isJetting();
|
||||
|
||||
// This flags that we need to go to the node location.
|
||||
mState.nextEdgePrecise = (mState.nextEdgeJetting || !nextEdge->isBorder());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Called when a node has been visited. Mark it for later avoidance (path randomizing)
|
||||
// if such is enabled and reasonable. Don't mark large terrain nodes or transients.
|
||||
void NavigationPath::randomization()
|
||||
{
|
||||
GraphNode * node = getNode(mState.curSeekNode);
|
||||
if (!node->transient() && node->getLevel() <= 2)
|
||||
node->setAvoid(60000);
|
||||
}
|
||||
|
||||
// The path is advanced through only two interface functions, this one and the next.
|
||||
void NavigationPath::advanceByOne()
|
||||
{
|
||||
AssertFatal(canAdvance(), "advanceByOne() called incorrectly");
|
||||
randomization();
|
||||
mState.curSeekNode++;
|
||||
setEdgeConstraints();
|
||||
}
|
||||
|
||||
// When user is done - they inform the system so it can advance.
|
||||
void NavigationPath::informJetDone()
|
||||
{
|
||||
AssertFatal(mState.thisEdgeJetting, "informJetDone() only called when jetting");
|
||||
randomization();
|
||||
mState.curSeekNode++;
|
||||
setEdgeConstraints();
|
||||
}
|
||||
|
||||
// User queries to see if they now must jet.
|
||||
bool NavigationPath::userMustJet() const
|
||||
{
|
||||
return (mState.path.size() > 0 && mState.thisEdgeJetting);
|
||||
}
|
||||
|
||||
void NavigationPath::forceSearch()
|
||||
{
|
||||
if (userMustJet() && _GRAPH_WARNINGS_)
|
||||
NavigationGraph::warning("Searched forced while user is jetting");
|
||||
mForceSearch = true;
|
||||
}
|
||||
|
||||
// Here we use the path randomization to hopefully get the guy off the path. We back up
|
||||
// through the list of any that were skipped - since we may not have even come to those
|
||||
// yet. And then we go forward by one.
|
||||
void NavigationPath::informStuck(const Point3F& stuckLoc, const Point3F& stuckDest)
|
||||
{
|
||||
stuckLoc; stuckDest;
|
||||
S32 start = getMax(mState.curSeekNode - 1, 1);
|
||||
S32 end = getMin(mState.curSeekNode + 1, mState.path.size() - 1);
|
||||
|
||||
while (start > 1 && mState.visit[start].test(State::Skipped))
|
||||
start--;
|
||||
|
||||
while (start < end) {
|
||||
GraphNode * node = getNode(start++);
|
||||
AssertFatal(!node->transient(), "informStuck() didn't skip transients");
|
||||
node->setAvoid(15 * 1000, true);
|
||||
}
|
||||
|
||||
mUserStuck = true;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------------------
|
||||
|
||||
U32 gCountStoppedTicks = 0;
|
||||
// F32 gVelSquaredThresh = 0.1;
|
||||
F32 gVelSquaredThresh = -1.0; // Take out for moment - needs more testing.
|
||||
|
||||
// When not moving but should be, this gets called - when in Express/Walk mode.
|
||||
void NavigationPath::informProgress(const VectorF& vel)
|
||||
{
|
||||
F32 velSquared = vel.lenSquared();
|
||||
if (velSquared < gVelSquaredThresh)
|
||||
{
|
||||
if ((mStopCounter += 2) > 10)
|
||||
gCountStoppedTicks++;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Once not stopped, then limit how much we have to count down. But we do
|
||||
// want that time as well for fading the running mCastAverage below.
|
||||
mStopCounter = getMin(mStopCounter, 7);
|
||||
}
|
||||
}
|
||||
|
||||
// A nice angle for scanning around with good coverage and consistent change. These
|
||||
// are relative prime numbers a little over 3/8 of the cycle points (360.0, 1.90000)
|
||||
static const F64 sCycleAngle = mDegToRad(F64(137.3));
|
||||
static const F64 sCycleHeight = 0.37747;
|
||||
static const U32 sCycleMask = (InteriorObjectType|TerrainObjectType);
|
||||
static const F32 sAverageTheNew = (1.0 / 8.0);
|
||||
static const F32 sAverageTheOld = (1.0 - sAverageTheOld);
|
||||
|
||||
// Monitor the stop counter. When it persists, look for walls and such to move
|
||||
// away from. We do this with rotating LOS checks, which also have to move up and
|
||||
// down to have the best chance of finding obstacles. Cycling on 1.9 height covers
|
||||
// range from 0.4 to 2.3 (see above addition to supplied here loc z).
|
||||
void NavigationPath::checkWallAvoid()
|
||||
{
|
||||
if (mStopCounter > 3)
|
||||
{
|
||||
if ((mCastAng += sCycleAngle) > M_2PI)
|
||||
mCastAng -= M_2PI;
|
||||
if ((mCastZ += sCycleHeight) > 1.9)
|
||||
mCastAng -= 1.9;
|
||||
|
||||
F32 angle32 = F32(mCastAng);
|
||||
Point3F startPoint( mState.hereLoc.x, mState.hereLoc.y, mState.hereLoc.z + F32(mCastZ) );
|
||||
Point3F lineOut( mCos(angle32), mSin(angle32), 0);
|
||||
|
||||
// Get the endpoint out a little ways. Find intersection and convert back
|
||||
// to an offset which we'll roll into running average of offsets.
|
||||
(lineOut *= 7.0) += startPoint;
|
||||
RayInfo coll;
|
||||
gServerContainer.castRay(startPoint, lineOut, sCycleMask, &coll);
|
||||
coll.point -= startPoint;
|
||||
|
||||
// Get the running average-
|
||||
coll.point *= sAverageTheNew;
|
||||
mCastAverage *= sAverageTheOld;
|
||||
mCastAverage += coll.point;
|
||||
}
|
||||
else
|
||||
{
|
||||
// In first few frames free, or first frames stuck, fade down the average.
|
||||
mCastAverage *= (2.0 / 3.0);
|
||||
}
|
||||
}
|
||||
|
||||
static Point3F reignIn(const Point3F& S, Point3F D, F32 cap)
|
||||
{
|
||||
D -= S;
|
||||
if (D.lenSquared() > (cap * cap))
|
||||
D.normalize(cap);
|
||||
return D += S;
|
||||
}
|
||||
|
||||
// This is where user fetches location to seek. We now do additional checks to
|
||||
// reign in this distance when near steep things.
|
||||
const Point3F& NavigationPath::getSeekLoc(const VectorF&)
|
||||
{
|
||||
// if (NavigationGraph::gotOneWeCanUse())
|
||||
// mAdjustSeek = reignIn(mState.hereLoc, mState.seekLoc, 10.0);
|
||||
// else
|
||||
if (mStopCounter)
|
||||
{
|
||||
// Seek away from collision point if that exists-
|
||||
mAdjustSeek = mCastAverage;
|
||||
mAdjustSeek *= F32(mStopCounter);
|
||||
mAdjustSeek += mState.hereLoc;
|
||||
}
|
||||
else
|
||||
mAdjustSeek = mState.seekLoc;
|
||||
|
||||
return mAdjustSeek;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------------------
|
||||
|
||||
// Convey the jetting ability to the jet manager. We let the aiConnection go ahead
|
||||
// and retrieve this data from Player since we've so far kept from #including Player.h.
|
||||
void NavigationPath::setJetAbility(const JetManager::Ability & ability)
|
||||
{
|
||||
if (gNavGraph) {
|
||||
if (_GRAPH_PART_ && gNavGraph->jetManager().update(mJetCaps, ability)) {
|
||||
// A bunch of work just happened, let's forestall searches a little-
|
||||
mRepathCounter = getMin(SearchWaitTicks >> 1, mRepathCounter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------------------
|
||||
|
||||
// How far across open terrain user can go from src to dst. dstLoc parameter is
|
||||
// updated accordingly, and a percentage from 0 to 1 is returned.
|
||||
F32 NavigationPath::checkOpenTerrain(const Point3F& srcLoc, Point3F& dstLoc)
|
||||
{
|
||||
if (NavigationGraph::gotOneWeCanUse())
|
||||
return gNavGraph->mTerrainInfo.checkOpenTerrain(srcLoc, dstLoc);
|
||||
dstLoc = srcLoc;
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------------------
|
||||
// A couple of path lookahead functions-
|
||||
|
||||
// Get a point that is dist along the path.
|
||||
Point3F NavigationPath::getLocOnPath(F32 dist)
|
||||
{
|
||||
Point3F currLoc(mState.hereLoc);
|
||||
|
||||
for (S32 i = mState.curSeekNode; i < mState.path.size() && dist > 0.01; i++)
|
||||
{
|
||||
Point3F nextLoc = getLoc(i);
|
||||
VectorF diffVec = nextLoc;
|
||||
F32 len = (diffVec -= currLoc).len();
|
||||
|
||||
if (len < dist)
|
||||
{
|
||||
currLoc = nextLoc;
|
||||
dist -= len;
|
||||
}
|
||||
else
|
||||
{
|
||||
currLoc += (diffVec *= (dist / len));
|
||||
break;
|
||||
}
|
||||
}
|
||||
return currLoc;
|
||||
}
|
||||
|
||||
// Find distance remaining on path by stepping through it - stop if user has given a
|
||||
// threshold dist outside of which they don't care (defaults to huge).
|
||||
F32 NavigationPath::distRemaining(F32 maxCare)
|
||||
{
|
||||
Point3F currLoc(mState.hereLoc);
|
||||
F32 dist = 0.0f;
|
||||
|
||||
for (S32 i = mState.curSeekNode; i < mState.path.size(); i++)
|
||||
{
|
||||
Point3F nextLoc = getLoc(i);
|
||||
|
||||
if ((dist += (nextLoc - currLoc).len()) > maxCare)
|
||||
return maxCare;
|
||||
|
||||
currLoc = nextLoc;
|
||||
}
|
||||
return dist;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------------------
|
||||
|
||||
// Find next edge that requires jetting if it's within maxDist. If found, then we
|
||||
// estimate the needed energy to complete the hop. Since the estimation is a little
|
||||
// lengthy - we remember which edge we computed for and only do it once.
|
||||
F32 NavigationPath::jetWillNeedEnergy(F32 maxDist)
|
||||
{
|
||||
if (!mState.thisEdgeJetting)
|
||||
{
|
||||
F32 accumDist = 0.0f;
|
||||
for (S32 i = mState.curSeekNode; i < mState.edges.size(); i++)
|
||||
{
|
||||
// Let's only do a len() calculation to the first node, otherwise use distance
|
||||
// on the edge. (First len() required because the bot's moving).
|
||||
F32 legDist;
|
||||
if (i == mState.curSeekNode)
|
||||
legDist = (getLoc(i) - mState.hereLoc).len();
|
||||
else
|
||||
legDist = mState.edges[i - 1]->mDist;
|
||||
|
||||
if ((accumDist += legDist) < maxDist)
|
||||
{
|
||||
GraphEdge * nextEdge = mState.edges[i];
|
||||
if (nextEdge->isJetting())
|
||||
return estimateEnergy(nextEdge);
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
// Get the jet manager's estimation, and remember that we did this work for this
|
||||
// edge since JetManager::estimateEnergy() does a bit o' math.
|
||||
F32 NavigationPath::estimateEnergy(const GraphEdge * edge)
|
||||
{
|
||||
if (mEstimatedEdge != edge)
|
||||
{
|
||||
mEstimatedEnergy = gNavGraph->jetManager().estimateEnergy(mJetCaps, edge);
|
||||
mEstimatedEdge = edge;
|
||||
}
|
||||
return mEstimatedEnergy;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------------------
|
||||
|
||||
// Return true if we are outside, and set the roam radius (in param) if so.
|
||||
bool NavigationPath::locationIsOutdoors(const Point3F &location, F32* roamRadPtr)
|
||||
{
|
||||
F32 roamRadius = 1e12; // Default is outdoors, with unlimited roam room.
|
||||
|
||||
if (NavigationGraph::gotOneWeCanUse())
|
||||
{
|
||||
if (!gNavGraph->haveTerrain())
|
||||
return false;
|
||||
|
||||
Point3F tempLocation(location.x, location.y, 0.0f);
|
||||
if (gNavGraph->mTerrainInfo.inGraphArea(tempLocation))
|
||||
{
|
||||
SphereF sphere;
|
||||
if (gNavGraph->mTerrainInfo.locToIndexAndSphere(sphere, tempLocation) >= 0)
|
||||
roamRadius = sphere.radius; // fall through to true
|
||||
else
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (roamRadPtr)
|
||||
*roamRadPtr = roamRadius;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// By default (with no graph), this is false.
|
||||
bool NavigationPath::weAreIndoors() const
|
||||
{
|
||||
if (NavigationGraph::gotOneWeCanUse())
|
||||
if (!gNavGraph->haveTerrain())
|
||||
return true;
|
||||
else if (gNavGraph->mTerrainInfo.inGraphArea(mState.hereLoc))
|
||||
return !gNavGraph->findTerrainNode(mState.hereLoc);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NavigationPath::getPathNodeLoc(S32 ahead, Point3F& loc)
|
||||
{
|
||||
S32 dest = mState.curSeekNode + ahead;
|
||||
|
||||
if (dest < mState.path.size() && dest > 0)
|
||||
{
|
||||
GraphEdge * beforeEdge = mState.edges[dest - 1];
|
||||
if (!beforeEdge->isJetting())
|
||||
{
|
||||
if (beforeEdge->isBorder())
|
||||
loc = gNavGraph->getBoundary(beforeEdge->mBorder).seekPt;
|
||||
else
|
||||
loc = getNode(dest)->location();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Does the bot have to jet into a vehicle?
|
||||
bool NavigationPath::intoMount() const
|
||||
{
|
||||
if (mLocateDest.isMounted())
|
||||
if (mState.curSeekNode == mState.path.size() - 1)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NavigationPath::canReachLoc(const Point3F& dst)
|
||||
{
|
||||
if (NavigationGraph::gotOneWeCanUse())
|
||||
{
|
||||
// mFindHere is a FindGraphNode which remembers results of closest node searches.
|
||||
mFindHere.setPoint(mState.hereLoc, mFindHere.closest());
|
||||
FindGraphNode findDest(dst);
|
||||
return gNavGraph->canReachLoc(mFindHere, findDest, mTeam, mJetCaps);
|
||||
}
|
||||
else
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void NavigationPath::missionCycleCleanup()
|
||||
{
|
||||
constructThis();
|
||||
mJetCaps.reset();
|
||||
mNavJetting.init();
|
||||
mHook.reset();
|
||||
mFindHere.init();
|
||||
mLocateHere.cleanup();
|
||||
mLocateDest.cleanup();
|
||||
}
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue