engine/ai/graphTransient.cc
2024-01-07 04:36:33 +00:00

319 lines
10 KiB
C++

//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#include "ai/graph.h"
//-------------------------------------------------------------------------------------
GraphEdge * GraphNode::pushTransientEdge(S32 dest)
{
// if (mEdges.isOwned())
// {
// AssertFatal(!mOwnedPtr, "GraphNode::pushTransientEdge()");
// mOwnedPtr = mEdges.address();
// mOwnedCnt = mEdges.size();
// mEdges.clearOwned();
// mEdges.reserve(mOwnedCnt + 4);
// mEdges.setSize(mOwnedCnt);
// dMemcpy(mEdges.address(), mOwnedPtr, mOwnedCnt * sizeof(GraphEdge));
// }
// Hook back to the supplied destination-
GraphEdge edge;
edge.mDest = dest;
edge.mDist = (mLoc - gNavGraph->lookupNode(dest)->location()).len();
mEdges.push_back(edge);
return & mEdges.last();
}
void GraphNode::popTransientEdge()
{
// if (mOwnedPtr)
// {
// AssertFatal(!mEdges.isOwned(), "GraphNode::popTransientEdge()");
// mEdges.setOwned(mOwnedPtr, mOwnedCnt, true);
// mOwnedPtr = NULL;
// mOwnedCnt = 0;
// }
// if (!mEdges.isOwned())
// mEdges.pop_back();
mEdges.pop_back();
}
//-------------------------------------------------------------------------------------
TransientNode::TransientNode(S32 index)
{
mGroundStart = -1;
mFlags.clear();
mFlags.set(Transient);
mIndex = index;
mClosest = NULL;
mSaveClosest = NULL;
}
Point3F TransientNode::getRenderPos() const
{
Point3F buff, adjusted;
adjusted = fetchLoc(buff);
adjusted.z += 0.2;
return adjusted;
}
// When asked for its list of edges, the transient node must return the list
// it used in the last search, not whatever it might currently think is it's
// best connection (it maintains these separately). This is a little confusing,
// might want to re-think if it's the best way to handle it.
GraphEdgeArray TransientNode::getEdges(GraphEdge*) const
{
return GraphEdgeArray(mSearchEdges.size(), mSearchEdges.address());
}
// This method is used below to get the current best hooks.
GraphEdgeArray TransientNode::getHookedEdges() const
{
return GraphEdgeArray(mEdges.size(), mEdges.address());
}
// This is weird, but I'm avoiding casts in the middle of the processing. Allows
// node traverser to handle transients Ok.
S32 TransientNode::volumeIndex() const
{
if (mSaveClosest && mSaveClosest->indoor())
return mSaveClosest->volumeIndex();
return -1;
}
//-------------------------------------------------------------------------------------
// Interface function for pairs of dynamic nodes
// Remove the two hook nodes referenced by the index. Should always be nodes there
// if proper version management (using 'incarnation' variables) is happening.
void NavigationGraph::destroyPairOfHooks(S32 pairLookup)
{
AssertFatal(validArrayIndex(pairLookup,mNodeList.size()-1) &&
mNodeList[pairLookup] && mNodeList[pairLookup + 1],
"Poor GraphHookRequest communication" );
for(S32 i = 0; i < 2; i++) {
delete mNodeList[pairLookup + i];
mNodeList[pairLookup + i] = NULL;
}
}
// Note that NULL entries are fleshed out at the end of graphMake (in doFinalFixups()).
S32 NavigationGraph::createPairOfHooks()
{
S32 findPair = mMaxTransients;
while( (findPair -= 2) >= 0 )
if(!mNodeList[ mTransientStart + findPair])
break;
AssertISV(findPair >= 0, "Out of dynamic graph connections (bot max exceeded?)");
findPair += mTransientStart;
AssertFatal(!mNodeList[findPair+1], "Free graph hooks must come in pairs");
// allocate 'em
for (S32 i = 0; i < 2; i++)
mNodeList[findPair + i] = new TransientNode(findPair + i);
return findPair;
}
//-------------------------------------------------------------------------------------
void mayBeFineButTrapIt()
{
}
// Transient nodes point INTO the graph, but the graph doesn't need or maintain hooks
// back, except when a search is performed - see navPath.cc.
//
// We also set the search edges here - we copy over those nodes that had LOS. If
// none have LOS, we keep at least the closest one.
//
S32 NavigationGraph::hookTransient(TransientNode& transient)
{
S32 retIsland = -7;
S32 thisIndex = transient.getIndex();
GraphEdgeArray edgeList = transient.getHookedEdges();
Point3F thisLoc = transient.mLoc;
transient.mSearchEdges.clear();
transient.mSaveClosest = transient.mClosest;
transient.mFirstHook = NULL;
while (GraphEdge * edge = edgeList++)
{
GraphNode * hookTo = lookupNode(edge->mDest);
S32 hookIsland = hookTo->mIsland;
Point3F destLoc = hookTo->location();
if((retIsland != -7) && (hookIsland != retIsland))
mayBeFineButTrapIt();
else
retIsland = hookIsland;
// The first hook is used as a representative node to determine reachability
// across partitions.
if (!transient.mFirstHook && !hookTo->transient())
transient.mFirstHook = hookTo;
if (GraphEdge * edgeBack = hookTo->pushTransientEdge(thisIndex))
{
// Last edge of path may contain a border. Note we have to reflect the
// border, which is easy since they come in pairs :)
if (edge->isBorder())
edgeBack->mBorder = (edge->mBorder ^ 1);
if (edge->isJetting())
edgeBack->setJetting();
if (edge->getTeam())
edgeBack->setTeam(edge->getTeam());
}
transient.mSearchEdges.push_back(*edge);
}
// restore the regular list.
transient.mEdges = transient.mSearchEdges;
return (transient.mIsland = retIsland);
}
void NavigationGraph::unhookTransient(TransientNode & transient)
{
GraphEdgeArray edgeList = transient.getEdges(NULL);
while (GraphEdge * edge = edgeList++)
lookupNode(edge->mDest)->popTransientEdge();
}
//-------------------------------------------------------------------------------------
// Balanced calls for hooking in pair of transients. Hook returns if the two locations
// can find each other (or if it's ambiguous). Determined by islands and partitions.
bool NavigationGraph::pushTransientPair(TransientNode& srcNode, TransientNode& dstNode,
U32 team, const JetManager::ID& jetCaps)
{
S32 island0 = hookTransient(dstNode);
S32 island1 = hookTransient(srcNode);
if (island0 == island1 && island0 >= 0)
{
// Islands Ok- check partitions-
S32 srcInd = srcNode.mFirstHook->getIndex();
S32 dstInd = dstNode.mFirstHook->getIndex();
GraphPartition::Answer answer;
jetCaps;
// First check armor partition- it never gives ambiguous answers (since they are
// computed in full when a new energy/armor configuration occurs).
#if _GRAPH_PART_
answer = mJetManager.reachable(jetCaps, srcInd, dstInd);
if (answer == GraphPartition::CanReach)
#endif
{
// Then check force fields. Answer can be ambiguous (in which case the search
// will try, and add to the partition list if it fails).
answer = mForceFields.reachable(team, srcInd, dstInd);
if (answer == GraphPartition::CanReach || answer == GraphPartition::Ambiguous)
return true;
}
}
return false;
}
void NavigationGraph::popTransientPair(TransientNode& srcNode, TransientNode& dstNode)
{
unhookTransient(srcNode);
unhookTransient(dstNode);
}
//-------------------------------------------------------------------------------------
bool NavigationGraph::canReachLoc(const FindGraphNode& src, const FindGraphNode& dst,
U32 team, const JetManager::ID& jetCaps)
{
GraphNode * srcNode = src.closest();
GraphNode * dstNode = dst.closest();
if (srcNode && dstNode) {
if (srcNode->island() == dstNode->island()) {
// Islands Ok- check partitions-
S32 srcInd = srcNode->getIndex();
S32 dstInd = dstNode->getIndex();
GraphPartition::Answer ans;
// First check armor partition- it never gives ambiguous answers-
#if _GRAPH_PART_
ans = mJetManager.reachable(jetCaps, srcInd, dstInd);
if (ans == GraphPartition::CanReach)
#endif
{
// Then check force fields. Answer can be ambiguous-
ans = mForceFields.reachable(team, srcInd, dstInd);
if (ans == GraphPartition::CanReach || ans == GraphPartition::Ambiguous)
return true;
}
}
}
return false;
}
//-------------------------------------------------------------------------------------
// A path searcher goes through this interface to get a handle to a transient node.
GraphHookRequest::GraphHookRequest()
{
mIncarnation = -1;
mPairLookup = -1;
}
GraphHookRequest::~GraphHookRequest()
{
if (NavigationGraph::gotOneWeCanUse())
if (gNavGraph->incarnation() == mIncarnation)
gNavGraph->destroyPairOfHooks(mPairLookup);
}
// Called on mission cycle - basically a re-construct.
void GraphHookRequest::reset()
{
mIncarnation = -1;
mPairLookup = -1;
}
// There CAN be GraphHookRequest objects constructed without there being a graph, but NOT
// actual requests for nodes. This is why we don't assert above, but do in the following.
TransientNode & GraphHookRequest::getHook(S32 firstOrSecond)
{
AssertFatal(NavigationGraph::gotOneWeCanUse(), "Called GraphHookRequest w/o graph");
if(mIncarnation != gNavGraph->incarnation()) {
mPairLookup = gNavGraph->createPairOfHooks();
mIncarnation = gNavGraph->incarnation();
}
GraphNode * theNode = gNavGraph->lookupNode(mPairLookup + firstOrSecond);
AssertFatal(mPairLookup >= 0 && theNode != NULL, "Couldn not alloc transient node");
AssertFatal(theNode->transient(), "Node must be transient!");
TransientNode * t = static_cast<TransientNode *>(theNode);
return *t;
}
bool GraphHookRequest::iGrowOld() const
{
AssertFatal( NavigationGraph::gotOneWeCanUse(),
"I hear the mermaids singing, each to each... "
"I do not think they sing to me... " );
return gNavGraph->incarnation() != mIncarnation;
}